I'm pretty sure I'm missing something completely obvious, but I can't see what. I watched WWDC session, read the Swift evolution blog, and they all make sense, but still it doesn't click for me. Please help me out here :) .
I'm diving into adopting the 'new' async/await style of coding (I know, it's old news at this point, but I could only get to it now), and so I'm all pumped to get my code to go eleven and therefore I wrote a small data-downloader class. It has one method, well two: one oldskool function with a completionHandler, and one new style async/await one.
When using the oldskool one, it works as everyone would expect:
print(1)
dataFetcher.fetchSomeData
{
print(2)
let data = $0
// process data ...
print(3)
}
print(4)
The output is, unsurprisingly:
1
4
2
3
Now, when I use my new style function:
let data = await dataFetcher.fetchSomeData()
// process data ...
Xcode gives me an error:
'async' call in a function that does not support concurrency
That makes sense, I am calling this in the viewDidLoad() method of a UIViewController subclass. Can't mark viewDidLoad() as await, as super's implementation is not async. No problem, let me wrap it in Task:
print(1)
Task
{
print(2)
let data = await dataFetcher.fetchSomeData()
// process data ...
print(3)
}
print(4)
No errors, and this code works exactly as expected, the output is:
1
4
2
3
So now I am wondering: why take the effort of changing/adding code for async/await style function that ultimately end up requiring exactly the same amount of code, and is exactly as non-linear as completionHandlers?
Note that the dataFetcher only has one property, an instance ofURLSession, so I am also not even managing my own queues or threads in the oldskool method vs the new one. They just wrap URLSession's functions:
func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
and
func download(for request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (URL, URLResponse)
Is async/await useful only with SwiftUI maybe? What am I missing here? Please help me see the light.
Post
Replies
Boosts
Views
Activity
I'm trying to get a view to move laterally, and when it has reached the end of that animation, snap (i.e. without animation) back to the starting position (seemingly instantly).
At the same time it snaps back, the view should display something different (in this example that is just text).
That last part disrupts what seemed to be a straightforward application of phaseAnimator, however, when changing the displayed text, the view's opacity goes to zero, moves (invisibly and instantly) to the new position and then opacity goes back to one.
My suspicion is that this has something to do with the way SwiftUI understands whether a view is the same or not via some kind of ID, but I'm not sure how to troubleshoot that.
Here is the code:
struct ContentView: View
{
@State
private var value: Int = 0
var body: some View
{
VStack
{
GeometryReader { geometry in
/// Replacing this with `Text("\(value)")` will cause the unintended effect
Text("Static text")
.phaseAnimator([0, 1], trigger: value) { view, phase in
view
.offset(x: phase == 1 ? 100 : 0)
} animation: { phase in
switch phase
{
case 1: .linear(duration: 1)
default: nil
}
}
}
Button("Start Animation")
{
value += 1
}
}
}
}
Any suggestions, or even solutions would be appreciated!
Hey all,
I have a very simple view that offsets some view based on the value of a binding. To calculate that offset, I also need to have the previous value of the binding. To have that, am using withAnimation().
I am also using a custom animation. To keep things simple, my custom animation right now is just a linear progression. I added some code to keep track of the number of times animate<>(:::) gets called.
Turns out, when I add .background(Color.green) to my Text(), the number of calls gets increased by 60 (per second of animation).
If .background(Color.green) gets added last (more precisely, after .offset(x: newOffset)), the background is not animated and the extra calls do not happen.
After reading the documentation and watching 'Demystifying SwiftUI', 'Demystifying SwiftUI performance' and various general SwiftUI and SwiftUI animation related WWDC sessions I am still feeling like I miss some basic understanding of SwiftUI animations.
Who can explain to me what is happening here and why? Or is the fact that animate<>(:::) gets called a number of times that is increasing linearly with the number of modifiers and number of subviews OK, and I should not be worried at all?
Relevant code below:
View:
struct TestView: View
{
@Binding
var offset: Double
@State
private var previousOffset: Double = 0
@State
private var isAnimatingToNewOffset = false
private let valuesInView = 20
var body: some View
{
GeometryReader { geometry in
let headingHeight: CGFloat = 80
let newOffset = isAnimatingToNewOffset ?
-(offset - previousOffset) / CGFloat(valuesInView) * geometry.size.width :
0
Text("Some text")
.frame(width: geometry.size.width)
.frame(height: headingHeight)
.offset(y: (geometry.size.height - headingHeight) / 2)
.background(Color.green) // moving this around, or removing it will cause the animate<>(:::) to be called a different number if times
.offset(x: newOffset)
}
.background(Color.gray)
.onChange(of: offset) { (oldValue, newValue) in
/// entering an animated change
isAnimatingToNewOffset = true
withAnimation(Animation(MyAnimation(duration: 1)))
{
// before updating, keep the value of the current offset
previousOffset = offSet
} completion:
{
// now update the previous offset to be ready for a new animation
previousOffset = offset
// trigger another update of the body, but now without animation
isAnimatingToNewOffset = false
}
}
}
}
My custom animation:
struct MyAnimation: CustomAnimation
{
/// just for debugging to understand how often this method gets called
private static var count = 0
let duration: TimeInterval
func animate<V>(value: V, time: TimeInterval, context: inout AnimationContext<V>) -> V? where V : VectorArithmetic
{
let relativeProgress = CGFloat(time / duration)
// print out the number of times this function is called, and with what value
print("\(String(format: "%02i", MyAnimation.count)) - \(String(format: "%1.2f", relativeProgress))")
MyAnimation.count += 1
guard time < duration else { return nil }
// keeping things simple for now, returning linear progress
return value.scaled(by: relativeProgress)
}
}
Hey folks,
Having some trouble getting Animations to behave properly. I'll try to explain what I am doing, but maybe it is easier to run the project, which you can clone from github here. The key is in commenting out line 32/33 and uncommenting lines 34/35 in GaugeView.swift
Otherwise: here is the high-level goal I'm aiming for:
I'd like to see a horizontal gauge, akin to a tape measure (ignore the colors in this example image ), but (initially) centered on zero, and extending both to negative and positive measures.
It will represent some value and that value can go up to a positive maximum number, and a minimum negative number. On overflow on either side, the tape measure should wrap-around.
For this, I would like to create the illusion of an infinite tape measure, that keeps moving as the values change.
There will be NO user interaction at all, the values are coming in from some device, and simply need to be displayed.
Now, the basic logic seems to work:
There is a tapemeasure view, which shows zero in the center. As a new value comes in, the view's offset gets animated to have the new value be in the center. At the end of the animation. The view's offset is reset to zero (without animation), and the view's content is updated to reflect the new center value.
In the current project there is only a single view, but the idea it that to the left and right of the visible view, there will be two other views to fill in the other parts. However, that is not relevant at the moment.
To support changes mid-animation, I've implemented a basic custom animation myself. This one allows me to measure how often animate<>(:::) gets called for now, but will be needed to make sure animations remain smooth when merging them.
The issue I run into is that that view with the actual contant contains a ForEach. And because of that, animate<>(:::) seems to be called many more times than I would expect. That number seems to be linearly related to the number of items in the ForEach. So I watched the WWDC session on view identy (Demystify SwiftUI), watched all animation-related sessions and tried to find some documatation on animations. Couldn't find what I was looking for.
When I replace the content view with a Rectangle(), the issue disappears.
So my question is: how to prevent a subview's changes to call animate(:::) so many times when a ForEach is present? Or maybe just block all internal animations of a subview? In essence I never need the content of the subview to be animated, only it's offset in the superview.
Hey folks,
I need to implement a view for a live activity (so using. WidgetKit), so any UIKit/SceneKit is a non-starter.
The thing to implement is a horizontal representation of a 360 degree protractor ('degree triangle').
I would like this view to be inifintely scrollable as if the user has a circular shape around their head and they can rotate their head to look at all the numbers, and when they reach 360/0, they simply continue with new round. In both directions.
I've managed to get this working for the positive values by using a LazyHStack and which is populated using ForEach, using a custom class that implements RandomAccessCollection where the endIndex is Int.max (not truly infinite, but good enough).
However, I can't get the scrollview/ForEach to start in the 'center' of the Int.max range (I'm sure this would cause some performance issue as well).
So my question is: how to get a SwiftUI scrollview to behave like a infinite scrollview, pretty much like Josh Shaffer explained in WWDC 2011 in the session with Eliza Block called 'Advanced ScrollView Techniques'?
Hey all,
In UIKit, we were able to specify options when using the UIView animation apis, and one of those options was .beginFromCurrentState.
I'm working on some animations in SwiftUI. This is a view that animates when some state gets updated. However, the animation is relatively long (2 seconds), and so that state might change mid-animation, triggering a new animation.
Currently, the ongoing animation simply gets stopped, and the view jumps to the starting point of the new animation immediately.
Is there a way to specify an animation to start from the current state of the view? In UIKit there is beginFromCurrentState, and in CoreAnimation we are able to use the presentation layer to achieve starting from currentState.
However, I am unable to find if / how to do this in SwiftUI.
Is this possible at all? If so, what documentation did I miss?
Hey all,
Tl;dr:
In debug-build using a .storekit file, all is good. When uploading a release build to testflight, purchasing follow the expected flow, but it does not seem the subscription status is reflected.
Full description
I've implemented auto-renewable subscriptions in my app. In debug, I've setup a scheme to use products.storekit. When I run this scheme on my device, I can subscrbe, cancel etc, and it all works as expected.
When I upload my release scheme to testflight, purchasing can be done, but changes do not seem to be reflected inside the app (i.e.e no features get unlocked).
Same thing when I run this scheme on my device. This scheme has None set for Storekit configuration.
When I set 'Storekit configuration' to the Products.storekit, file I can't make a purches at all: SubscriptionStoreView shows
Subscription unavailable. The subscription is unavailable in the current storefront'.
I've watched a number of WWDC sessions on the topic, read most (all?) of the documentation, and I just can't seem to find out what it is that needs to be done.
Unfortunately, debugging has been rather cumbersome and sometimes impossible lately, especially when trying to debug a release build.
Anyone can tell me what it is I am overlooking, or what piece of information or link I missed?
Here is the relevant code (which works OK in debug)
private func listenForTransactions() -> Task<Void, Error>
{
return Task.detached
{
for await anUpdate in Transaction.updates
{
do
{
let transaction = try self.checkVerified(anUpdate)
await self.checkSubscriptionStatus()
await transaction.finish()
}
}
}
}
And here's checkSubscriptionStatus():
@MainActor
private func checkSubscriptionStatus() async
{
var hasActiveSubscription = false
do
{
for aStatus in try await Product.SubscriptionInfo.status(for: "718A7488")
{
let state = aStatus.state
hasActiveSubscription = (state == .inGracePeriod) || (state == .subscribed)
}
}
catch
{
print(error)
}
self.hasActiveSubscription = hasActiveSubscription
}
Hi all,
I've implemented in-app purchases to have auto-renewing subscriptions.
However, when I use Xcode's Transaction Manager to expire a subscription, there seems to be no callback Transaction.updates or Transaction.currentEntitlements
I can see an expiry through Transaction.all but I can't imagine the intention is to poll that while the app is running.
What am I missing here?
Xcode Version 15.2 in general is very, very slow when debugging. If I get to that, often, xcode just shows a message "Preparing iPhone 15 Pro Joride. Xcode will continue when the operation completes"
This box will stay there for at least 3 min. Then it is follow by a message about a developer image not being able to get mounted: "Previous preparation error: The developer disk image could not be mounted on this device.. Error mounting image: 0xe8000115 (kAMDMobileImageMounterDevicePropertyQueryFailure: Failed to query device property.)"
If I eventually do manage to debug my app, it is soooooo slow. The launchscreen is there for easily 20 seconds (I'm guessing debugger is trying to attach during this time). When there are breakpoints present at all, things just occur very very slowly, expecially when trying to PO variables. The app itself runs fine though.
I've noticed there is a checkbox for this device that says 'Connect via network'. This clearly seems to be broken for my combination of factors, so I wanted to uncheck it. Not possible!
For those of use with a similar experience, any suggestions? (I did try to power cycle both computer and iPhone, but that did not change much. Both devices are connected to the same home wifi which has no special setup or restrictions)
Hi all,
I'm using AVPictureInPictureController to show CMSampleBuffers. No problems with that.
When the Picture in Picture view/window is active and the phone is locked for long enough (~30 seconds), the Picture in Picture view/window will show black. I've noticed the same happens with Netflix, so I assume there is no way around that.
So when that happens (i.e. Picture in Picture view/window will showing black), I would like to just stop Picture-in-Picture mode. For this, I would need to be able to detect when Picture in Picture view/window will starts showing black pixels.
I've tried listening for UIApplication lifetime events (such as didResignActive. and didMoveToBackground), but as my app is a location-based app that received Location updates in the background, those are not at all an indicator of the black PiP window.
Any suggestions for me on how to handle the PiP window showing a black window upon unlocking the phone?
As part of a hobby project, I'm working on a 2D game engine that will draw each pixel every frame, using a color from a palette. I am looking for a way to do that while maintaining a reasonable frame rate (60fps being the minimum).
Without any game-logic in place, I am updating the values of my pixels with some value form the palette. I'm currently taking the mod of an index, to (hopefully) prevent the compiler from doing some loop-optimisation it could do with a fixed value.
My very naive implementation of updating the bytes in the pixel array goes like this. On an iPhone 12 Pro, each run of updating all pixel values takes on average 43 ms, while on a simulator running on an M1 mac, it takes 15 ms. Both unacceptable, as that would leave not for any additional game logic (which would be much more operations than taking the mod of an Int).
I was planning to look into Metal and set up a surface, but clearly the bottleneck here is the CPU, so if I can optimize this code, I could go for a higher-level framework.
Any suggestions on a performant way to write this many bytes much, much faster (parallelisation is not an option)?
struct BGRA
{
let blue: UInt8
let green: UInt8
let red: UInt8
let alpha: UInt8
}
let BGRAPallet =
[
BGRA(blue: 124, green: 124, red: 124, alpha: 0xff),
BGRA(blue: 252, green: 0, red: 0, alpha: 0xff),
// ... 62 more values in my code, omitting here for brevity
]
private func test()
{
let pixelBufferPtr = UnsafeMutableBufferPointer<BGRA>.allocate(capacity: screenWidth * screenHeight)
let runCount = 1000
let start = Date.now
for _ in 0 ..< runCount
{
for index in 0 ..< pixelBufferPtr.count
{
pixelBufferPtr[index] = BGRAPallet[index % BGRAPallet.count]
}
}
let elapsed = Date.now.timeIntervalSince(start)
print("Average time per run: \((Int(elapsed) * 1000) / runCount) ms")
}
As a proof of concept, I am trying to create bare-bones mac-command line app (in Swift) that allows me to send a string as push notification to my own iPhone.
I'm trying to follow the guide from Apple, however, I'm stuck creating the Jason Web Token.
The guide kindly refers to JWT documentation, and leaves the most difficult part as an exercise to the user: actually creating the JWT (turning the difficulty up to 11 for me ;) ).
I've tried a number of things (mostly by scraping on the internets, sadly), but when I try to verify my token at the [JWT site](https://jwt. io), is always invalid.
High level, here is my understanding (let's sanity check my thinking, before diving into details:
Create 3 JSON-strings: header, claims and payload
Base64 encode each one separately
Combine these three pieces of data like so
let Data((headerBase64String + "." + claimsBase64String + "." + payloadBase64String).utf8)
Encrypt this this data using an algorithm called 'ES256' (no idea how to do this, could not find any documentation within Cryptokit on this one).
Print the data as string, and enter the results into [JWT site](https://jwt. io). No joy.
I would like to use nothing but default libraries (i.e. Foundation, Cryptokit etc), and not make use of IBM's Kibana project (which is what majority of online suggestions seem to be doing). I'm aiming for a thorough understanding here.
Where do I need help to kickstart my understanding?
In my app, users can select from a range of UI color-palettes to customize the look of the app to suite their preference.
The app's root viewcontroller is a UINavigationController (this app is not using SwiftUI, nor has it adopted UIScene yet).
However, I noticed only the first time (to be specific: this is in the appDelegate's application(:didFinishLaunchingWithOptions:), so before anything is actually rendered to the screen) the appearance settings are applied, but subsequent changes do not take effect.
However, the following code seems to 'fix' the issue:
self.window?.rootViewController = nil
self.updateAppearance()
self.window?.rootViewController = navigationController
This to me seems hacky/kludgy and unlikely to be a sustainable 'fix', and I think it indicates that the appearance information is only taken into account when a view(controller) gets inserted into the view(controller) hierarchy, in other words: when it gets drawn.
So then the question would be: how to tell a UINavigationController / Bar to redraw using the updated appearance settings?
I'm sure I'm overlooking something relatively simple (as I imagine this kind of thing is something many apps do to support darkmode), but alas, I can't seem to find it.
I setup a small test-project to isolate this issue. It has only one custom viewcontroller (ContentViewController) which only sets the backgroundColor in viewDidLoad(), and an AppDelegate (code below). No storyboards, no UIScene related things.
So, here is the minimal code that reproduces my issue:
AppDelegate.swift:
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate
{
var window: UIWindow?
var rootNavigationController: UINavigationController?
let contentViewController = ViewController()
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
window = UIWindow()
let navigationController = UINavigationController(rootViewController: contentViewController)
rootNavigationController = navigationController
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
// initial setup of appearance, this first call will take effect as expected
updateAppearance()
// setup a timer to repeatedly change the appearance of the navigationbar
timer.setEventHandler
{
/// ! uncommenting this line and the one after `self.updateAppearance()`
/// will cause `self.updateAppearance()` to take effect as expected
// self.window?.rootViewController = nil
self.updateAppearance()
//self.window?.rootViewController = navigationController
}
timer.schedule(deadline: .now(),
repeating: .seconds(2),
leeway: .milliseconds(50))
timer.resume()
return true
}
// some vars to automatically toggle between colors / textstyles
var colorIndex = 0
let colors = [ UIColor.blue, UIColor.yellow]
let textStyles: [UIFont.TextStyle] = [.title3, .body]
func updateAppearance()
{
let color = colors[colorIndex]
let textStyle = textStyles[colorIndex]
contentViewController.title = "bgColor: \(colorIndex) \(textStyle.rawValue)"
colorIndex = (colorIndex == 0) ? 1 : 0
let textColor = colors[colorIndex]
self.rootNavigationController?.navigationBar.isTranslucent = false
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.backgroundColor = color
let textAttributes: [NSAttributedString.Key : Any] =
[.font: UIFont.preferredFont(forTextStyle: textStyle),
.foregroundColor: textColor ]
navigationBarAppearance.titleTextAttributes = textAttributes
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
UINavigationBar.appearance().compactAppearance = navigationBarAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
}
}
ViewController:
import UIKit
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
}
}
As per the title, but let me provide some more context:
on macos, using the Photos app, I can change the ‘poster frame, for a video. This frame is the one displayed as a thumbnail.
on iOS’ Photos app this functionality does not (see to?) exist.
So, to solve my own problem, I would like to build an app that does just that.
I am not sure how ‘poster frame’ is implemented, and have no idea where to start looking (perusing the PhotoKit and AVAssetWriter documentation, I didn’t find any hints on ‘poster frame’.
I am just looking for some pointers at this point, to understand if this is at all possible.
Hey all,
I'm currently investigating whether to integrate an app with HealthKit.
I can't seem to find how a dev would go about testing that integration.
This app would only be writing data to HealthKit, not reading from it.
What is a good dev-strategy for working with HealthKit? "No need, non-production builds write into some kind of sandbox, does not affect 'real' HK data" ?
Use a separate device + iCloud account
Write some test/dev data, and delete
Any suggestion (or links to docs that I missed) are much appreciated!