I have a couple of side projects going on, although it is always a challenge to find time of them. One of them, SignalPath, is what I created back in 2015. Currently, I have been spending some time to bump the Swift version to 6 which brought a quite a list of errors. In many places I had code what dealt with observing multiple notifications, but of course Swift 6 was not happy about it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
After moving all of the notification observing to publishers instead, I can ignore the whole sendable closure problem all together.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Great, compiler is happy again although this code could cause trouble if any of the notifications are posted from a background thread. But since this is not a case here, I went for skipping .receive(on: DispatchQueue.main). Assign weakly is a custom operator and the implementation looks like this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AnyClass is a protocol all classes conform to and it comes with a feature I was not aware of. But first, how to I ended up with using AnyClass. While working on code using CoreData, I needed a way to enumerate all the CoreData entities and call a static function on them. If that function is defined, it runs an entity specific update. Let’s call the function static func resetState().
It is easy to get the list of entity names of the model and then turn them into AnyClass instances using the NSClassFromString() function.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
At this point I had an array of AnyClass instances where some of them implemented the resetState function, some didn’t. While browsing the AnyClass documentation, I saw this:
You can use the AnyClass protocol as the concrete type for an instance of any class. When you do, all known @objcclass methods and properties are available as implicitly unwrapped optional methods and properties, respectively.
Never heard about it, probably because I have never really needed to interact with AnyClass in such way. Therefore, If I create an @objc static function then I can call it by unwrapping it with ?. Without unwrapping it safely, it would crash because Department type does not implement the function.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
It has been awhile since I wrote any Objective-C code, but its features leaking into Swift helped me out here. Reminds me of days filled with respondsToSelector and performSelector.
Loved to see this entry in Xcode 16’s release notes:
Xcode 16 brings a new execution engine for Previews that supports a larger range of projects and configurations. Now with shared build products between Build and Run and Previews, switching between the two is instant. Performance between edits in the source code is also improved for many projects, with increases up to 30%.
It has been difficult at times to use SwiftUI previews when they sometimes just stop working with error messages leaving scratch head. Turns out, it comes with a hidden cost of Xcode 16 wrapping views with AnyView in debug builds which takes away performance. If you don’t know it only affects debug builds, one could end up on journey of trying to improve the performance for debug builds and making things worse for release builds. Not sure if this was ever mentioned in any of the WWDC videos, but feels like this kind of change should have been highlighted.
As of Xcode 16, every SwiftUI view is wrapped in an AnyView _in debug builds only_. This speeds switching between previews, simulator, and device, but subverts some List optimizations.
Add this custom build setting to the project to override the new behavior:
`SWIFT_ENABLE_OPAQUE_TYPE_ERASURE=NO`
Wrapping in Equatable is likely to make performance worse as it introduces an extra view in the hierarchy for every row.
Swift’s foundation library provides a sorted(by:) function for sorting arrays. The areInIncreasingOrder closure needs to return true if the closure’s arguments are increasing, false otherwise. How to use the closure for sorting by multiple criteria? Let’s take a look at an example of sorting an array of Player structs.
Sort by score in descending order
Sort by name in ascending order
Sort by id in ascending order
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
As said before, the closure should return true if the left element should be ordered before the right element. If they happen to be equal, we should use the next sorting criteria. For comparing strings, we’ll go for case-insensitive sorting using Foundation’s built-in localizedCaseInsensitiveCompare.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DateFormatter is used for converting string representation of date and time to a Date type and visa-versa. Something to be aware of is that the conversion loses microseconds precision. This is extremely important if we use these Date values for sorting and therefore ending up with incorrect order. Let’s consider an iOS app which uses API for fetching a list of items and each of the item contains a timestamp used for sorting the list. Often, these timestamps have the ISO8601 format like 2024-09-21T10:32:32.113123Z. Foundation framework has a dedicated formatter for parsing these strings: ISO8601DateFormatter. It is simple to use:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Great, but there is on caveat, it ignores microseconds. Fortunately this can be fixed by manually parsing microseconds and adding the missing precision to the converted Date value. Here is an example, how to do this using an extension.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
That this code does is first converting the string using the original date(from:) method, followed by manually extracting digits for microseconds by handling cases where there are less than 3 digits or event there are nanoseconds present. Lastly a new Date value is created with the microseconds precision. Here are examples of the output (note that float’s precision comes into play).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
It is not often when we need to wrap an async function with a completion handler. Typically, the reverse is what happens. This need can happen in codebases where the public interface can’t change just right now, but internally it is moving towards async-await functions. Let’s jump in and see how to wrap an async function, an async throwing function and an async throwing function what returns a value.
To illustrate how to use it, we’ll see an example of how a PhotoEffectApplier type has a public interface consisting of completion handler based functions and how it internally uses PhotoProcessor type what only has async functions. The end result looks like this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
In this example, we have all the interested function types covered: async, async throwing and async throwing with a return type. Great, but let’s have a look at these Task initializers what make this happen. The core idea is to create a Task, run an operation, and then make a completion handler callback. Since most of the time we need to run the completion on the main thread, then we have a queue argument with the default queue set to the main thread.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Observation framework came out along with iOS 17 in 2023. Using this framework, we can make objects observable very easily. Please refer to @Observable macro in SwiftUI for quick recap if needed. It also has a function withObservationTracking(_:onChange:) what can be used for cases where we would want to manually get a callback when a tracked property is about to change. This function works as a one shot function and the onChange closure is called only once. Note that it is called before the value has actually changed. If we want to get the changed value, we would need to read the value on the next run loop cycle. It would be much more useful if we could use this function in a way where we could have an observation token and as long as it is set, the observation is active. Here is the function with cancellation support.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The apply closure drives which values are being tracked, and this is passed into the existing withObservationTracking(_:onChange:) function. The token closure controls if the change should be handled and if we need to continue tracking. Will and did change are closures called before and after the value has changed.
Here is a simple example where we have a view which controls if the observation should be active or not. Changing the value in the view model only triggers the print lines when observation token is set.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
It took a long time, I mean years, but it finally happened. I stumbled on a struct which had a property of the same type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
At first, it is kind of interesting that the replies property compiles fine, although it is a collection of the same type. I guess it is so because array’s storage type is a reference type.
The simplest workaround is to use a closure for capturing the actual value.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Or if we prefer property wrappers, using that instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
In addition to scroll related view modifiers covered in the previous blog post, there is another one for detecting scroll view phases aka the state of the scrolling. The new view modifier is called onScrollPhaseChange(_:) and has three arguments in the change closure: old phase, new phase and a context.
decelerating – user interaction stopped and scroll velocity is decelerating
idle – no scrolling
interacting – user is interacting
tracking – potential user initiated scroll event is going to happen
The enum has a convenience property of isScrolling which is true when the phase is not idle.
ScrollPhaseChangeContext captures additional information about the scroll state, and it is the third argument of the closure. The type gives access to the current ScrollGeometry and the velocity of the scroll view.
Here is an example of a scroll view which has the new view modifier attached.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
WWDC’24 brought some updates to scrolling. One of which is onScrollGeometryChange(for:of:action:) what we can use for reacting to scroll geometry changes. The view modifier has two closures, where the first one is transforming the scroll geometry into an arbitrary equatable type of our liking. If that value changes, the action closure is called. It is a convenient way for triggering view updates or updating other states.
The new ScrollGeometry type provides the current scroll state:
bounds
containerSize
contentInsets
contentOffset
contentSize
visibleRect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here is another example where we can use the new modifier for showing a scroll to top button in combination with the new scrollPosition(_:anchor:) view modifier.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters