Categories
iOS Swift SwiftUI

Bizarre error in SwiftUI preview

The other day, I was playing around with matchedGeometryEffect view modifier in my sample app. I was just planning to show a list of items and then animate moving one item from one HStack to another. Suddenly, my SwiftUI preview stopped working. On the other hand, running exactly the same code on the simulator just worked fine. The code was very simple, consisting of view, view model and an Item model struct.

import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
VStack {
ForEach(viewModel.items) { item in
Text(verbatim: item.name)
}
}
.padding()
}
}
extension ContentView {
final class ViewModel: ObservableObject {
let items: [Item] = [
Item(name: "first"),
Item(name: "second")
]
func select(_ item: Item) {
// implement
}
}
struct Item: Identifiable {
let name: String
var id: String { name }
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

If you try to render SwiftUI preview (I was using Xcode 14.3) then Xcode is giving up with “Failed to launch the app XXX in reasonable time”. But if I try to build and run it on simulator, it just works fine. After some trial and error, it turned out that SwiftUI previews broke as soon as I added the func select(_ item: Item) function. If you pay a close attention, then you can see that the longer type name for Item is ContentView.Item, but within the ContentView.ViewModel type I am using just Item. I do not know why, but SwiftUI previews seems to get confused by it. As soon as I change the function declaration to func select(_ item: ContentView.Item) the preview starts rendering again. Another way is declaring the Item struct outside the ContentView extension.

The learning point is that if SwiftUI previews stop working suddenly, then make sure to check how nested types are used in function declarations.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
CoreData iOS Swift SwiftUI

NSFetchedResultsController wrapper for SwiftUI view models

Although SwiftUI framework contains a @FetchRequest property wrapper, it is not possible to use it outside of SwiftUI views because it relies on accessing the managed object context from the view environment. While working on an CoreData based app which uses view models a lot within SwiftUI views, I ended up creating a wrapper class for making it easier to use NSFetchedResultsController. The wrapper class is named FetchedResultList and what it does internally is creating a NSFetchedResultsController instance, handling its delegate methods, and notifying about data changes through closures. Here is an example of a view model using the wrapper.

@MainActor final class ViewModel: ObservableObject {
private let list: FetchedResultList<Fruit>
init(context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
list = FetchedResultList(context: context,
sortDescriptors: [
NSSortDescriptor(keyPath: \Fruit.name, ascending: true)
])
list.willChange = { [weak self] in self?.objectWillChange.send() }
}
var fruits: [Fruit] {
list.items
}
@Published var searchText: String = "" {
didSet {
if searchText.isEmpty {
list.predicate = nil
}
else {
list.predicate = NSPredicate(format: "name contains[cd] %@", searchText)
}
}
}
}
view raw Fruits.swift hosted with ❤ by GitHub

The view model conforms to ObservableObject protocol and FetchedResultList provides a willChange closure which gets called when NSFetchedResultsController’s will change delegate is called. Calling the objectWillChange publisher will signal the SwiftUI view about the data change and the view gets re-rendered. The wrapper also supports updating predicate or sort descriptors dynamically. Here, we can see how setting a searchableText property from the view will update the predicate of the wrapper class. The SwiftUI view which uses the view model looks like this:

struct FruitList: View {
@StateObject var viewModel = ViewModel()
var body: some View {
NavigationStack {
List(viewModel.fruits) { item in
NavigationLink(item.name) {
Text(verbatim: "Detail view for \(item.name)")
}
}
.navigationTitle("Fruits")
.searchable(text: $viewModel.searchText)
}
}
}
view raw Fruits.swift hosted with ❤ by GitHub

Let’s take a look at how the wrapper class is implemented. The core logic around the wrapper is creating an instance of NSFetchedResultsController, allowing to reconfigure it dynamically, handling its delegate, and notifying changes through closures. Using closures instead of conforming to ObservableObject is a conscious choice since the main use case is using the wrapper class in view models or in other controllers, and it means that propagating the data change to a SwiftUI view needs to be done manually. It is shorter to call view model’s objectWillChange in a closure than republishing wrapper’s objectWillChange to view model’s objectWillChange. Moreover, it would make more sense to use SwiftUI provided property wrapper instead of this wrapper if we would want to handle CoreData fetching in the SwiftUI view’s implementation.

@MainActor final class FetchedResultList<Result: NSManagedObject> {
private let fetchedResultsController: NSFetchedResultsController<Result>
private let observer: FetchedResultsObserver<Result>
init(context: NSManagedObjectContext, filter: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]) {
let request = NSFetchRequest<Result>(entityName: Result.entity().name ?? "<not set>")
request.predicate = filter
request.sortDescriptors = sortDescriptors.isEmpty ? nil : sortDescriptors
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
observer = FetchedResultsObserver(controller: fetchedResultsController)
observer.willChange = { [unowned self] in self.willChange?() }
observer.didChange = { [unowned self] in self.didChange?() }
refresh()
}
private func refresh() {
do {
try fetchedResultsController.performFetch()
}
catch {
Logger().error("Failed to load results")
}
}
var items: [Result] {
fetchedResultsController.fetchedObjects ?? []
}
var predicate: NSPredicate? {
get {
fetchedResultsController.fetchRequest.predicate
}
set {
fetchedResultsController.fetchRequest.predicate = newValue
refresh()
}
}
var sortDescriptors: [NSSortDescriptor] {
get {
fetchedResultsController.fetchRequest.sortDescriptors ?? []
}
set {
fetchedResultsController.fetchRequest.sortDescriptors = newValue.isEmpty ? nil : newValue
refresh()
}
}
var willChange: (() -> Void)?
var didChange: (() -> Void)?
}

The wrapper class uses private FetchedResultsObserver class which must derive from NSObject because it implements NSFetchedResultsControllerDelegate methods. This approach allows keeping the FetchedResultList class a pure Swift class and not a NSObject subclass which I like to avoid in SwiftUI apps (just a personal preference).

private final class FetchedResultsObserver<Result: NSManagedObject>: NSObject, NSFetchedResultsControllerDelegate {
var willChange: () -> Void = {}
var didChange: () -> Void = {}
init(controller: NSFetchedResultsController<Result>) {
super.init()
controller.delegate = self
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
willChange()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
didChange()
}
}

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift

Async-await and completion handler compatibility in Swift

The prominent way for writing async code before async-await arrived to Swift was using completion handlers. We pass in a completion handler which then gets called at some later time. When working with larger codebases, it is not straight-forward to convert existing code to use newer techniques like async-await. Often we make these changes over time, which means that in case of wrapping completion handler based code, we would have the same function both in form of completion handler and async await. Fortunately, it is easy to wrap existing completion handler based code and to provide an async-await version. The withCheckedThrowingContinuation() function is exactly for that use-case. It provides an object which will receive the output of our completion handler based code – most of the time a value or an error. If we use Result type in completion handlers, then it is only 3 lines of code to wrap it, thanks to the fact that the continuation has a dedicated function for resuming result types.

final class ImageFetcher {
func fetchImages(for identifiers: [String], completionHandler: @escaping (Result<[String: UIImage], Error>) -> Void) {
// …
}
}
extension ImageFetcher {
func fetchImages(for identifiers: [String]) async throws -> [String: UIImage] {
try await withCheckedThrowingContinuation { continuation in
fetchImages(for: identifiers) { result in
continuation.resume(with: result)
}
}
}
}

Great, but what if we add new code to an existing code base relying heavily on completion handler based code? Can we start with an async function and wrap that as well? Sure. In the example below, we have some sort of DataFetcher which has an async function. If we needed to call this function from a completion handler based code, we can add a wrapping function pretty easily. Later, if we have fully converted to async-await, it can be discarded easily. So how do we do it? We start off the wrapping code by creating a Task which starts running automatically and which also provides an async context for calling async functions. This means that we can call the async function with try await and catching the error if it throws. Then it is just a matter of calling the completion handler. Depends on the use-case and how this code is meant to be used, but we should always think about which thread should be calling the completion handler. In the example, we always switch to the main thread because the Task’s closure is running on a global actor (in other words, on a background thread).

final class DataFetcher {
func fetchData(for identifiers: [String]) async throws -> [String: Data] {
// …
}
}
extension DataFetcher {
func fetchData(for identifiers: [String], completionHandler: @escaping (Result<[String: Data], Error>) -> Void) {
Task {
do {
let data = try await fetchData(for: identifiers)
await MainActor.run {
completionHandler(.success(data))
}
}
catch {
await MainActor.run {
completionHandler(.failure(error))
}
}
}
}
}

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift

TaskGroup error handling in Swift

Task groups in Swift are used for running n-number of child tasks and letting it handle things like cancellation or different priorities. Task groups are created with either withThrowingTaskGroup(of:returning:body:) or withTaskGroup(of:returning:body:). The latter is for cases when errors are not thrown. In this blog post, we will observe two cases of generating Data objects using a task group. In the first case, we want to stop the group as soon as an error has occurred and discard all the remaining work. The other case looks at ignoring any errors in child tasks and collect just collecting Data objects for tasks which were successful.

The example we are going to use simulates creating image data for multiple identifiers and then returning an array of Data objects. The actual image creating and processing is simulated with Task’s sleep function. Since task groups coordinate cancellation to all the child tasks, then the processor implementation also calls Task.checkCancellation() to react to cancellation and stopping as soon as possible for avoiding unnecessary work.

struct ImageProcessor {
static func process(identifier: Int) async throws -> Data {
// Read image data
try await Task.sleep(nanoseconds: UInt64(identifier) * UInt64(1e8))
try Task.checkCancellation()
// Simulate processing the data and transforming it
try await Task.sleep(nanoseconds: UInt64(1e8))
try Task.checkCancellation()
if identifier != 2 {
print("Success: \(identifier)")
return Data()
}
else {
print("Failing: \(identifier)")
throw ProcessingError.invalidData
}
}
enum ProcessingError: Error {
case invalidData
}
}

Now we have the processor created. Let’s see an example of calling this function from a task group. As soon as we detect an error in one of the child tasks, we would like to stop processing and return an error from the task group.

let imageDatas = try await withThrowingTaskGroup(of: Data.self, returning: [Data].self) { group in
imageIdentifiers.forEach { imageIdentifier in
group.addTask {
return try await ImageProcessor.process(identifier: imageIdentifier)
}
}
var results = [Data]()
for try await imageData in group {
results.append(imageData)
}
return results
}
view raw Case1.swift hosted with ❤ by GitHub

We loop over the imageIdentifiers array and create a child task for each of these. When child tasks are created and running, we wait for child tasks to finish by looping over the group and waiting each of the child task. If the child task throws an error, then in the for loop we re-throw the error which makes the task group to cancel all the remaining child tasks and then return the error to the caller. Since we loop over each of the task and wait until it finishes, then the group will throw an error of the first added failing task. Also, just to remind that cancellation needs to be handled explicitly by the child task’s implementation by calling Task.checkCancellation().

Great, but what if we would like to ignore errors in child tasks and just collect Data objects of all the successful tasks. This could be implemented with withTaskGroup function by specifying the child task’s return type optional and handling the error within the child task’s closure. If error is thrown, return nil, and later when looping over child tasks, ignore nil values with AsyncSequence’s compactMap().

let imageDatas = await withTaskGroup(of: Data?.self, returning: [Data].self) { group in
imageIdentifiers.forEach { imageIdentifier in
group.addTask {
do {
return try await ImageProcessor.process(identifier: imageIdentifier)
} catch {
return nil
}
}
}
var results = [Data]()
for await imageData in group.compactMap({ $0 }) {
results.append(imageData)
}
return results
}
view raw Case2.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift SwiftUI

Grid view in SwiftUI

SwiftUI framework got a new addition in iOS 16 named Grid which is enables creating grid layouts in a quick way. The main difference between using Grid instead of combining HStack and VStack is that all the cells will get the same size. SwiftUI will create all the cells, measure the size, and apply the largest size to all the cells. Here is an example of a grid which in addition to cells have additional accessory views.

struct ContentView: View {
var body: some View {
Grid(alignment: .center,
horizontalSpacing: 16,
verticalSpacing: 8) {
Separator(title: "Today")
.gridCellUnsizedAxes(.horizontal)
GridRow {
Text("Finals")
Cell(title: "Archery", imageName: "figure.archery")
Cell(title: "Badminton", imageName: "figure.badminton")
}
Separator(title: "Tomorrow")
.gridCellUnsizedAxes(.horizontal)
GridRow(alignment: .bottom) {
Text("Qualifications")
Cell(title: "Bowling", imageName: "figure.archery")
Cell(title: "Golf", imageName: "figure.golf")
Cell(title: "Handball", imageName: "figure.handball")
}
}
}
}
view raw Grid.swift hosted with ❤ by GitHub

In the example above we can see how Grid is created: first we have the Grid container view with one or multiple GridRow views which represents a single row of cells. If we want to decorate the grid with accessory views, we can just add more views which are not wrapped into GridRow. Separator view is just a view which displays text and a divider. The gridCellUnsizedAxes() allows controlling how these accessory views are laid out. Separator contains a Divider, which is a flexible view and wants to take as much space it could. By applying the view modifier we can disable this behaviour and the width of the accessory view is not limited by the number of columns in the grid.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift SwiftUI

Presenting multiple sheets in SwiftUI

SwiftUI has multiple view modifiers for presenting sheets. If we just want to present a modal view, then we can use one of these:

func sheet<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: @escaping () -> Content
) -> some View where Content : View
func sheet<Item, Content>(
item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
content: @escaping (Item) -> Content
) -> some View where Item : Identifiable, Content : View
view raw Sheets.swift hosted with ❤ by GitHub

The first requires a boolean binding, whereas the second an identifiable item. When dealing with views which need to present different views in a sheet, then the latter can be easily expanded to support that. We can create an enum, conform it to Identifiable and then add an optional @State property which selects the view we should be presenting. The Identifiable protocol requires implementing an id property, which we can easily do by reusing rawValue property of an enum with raw types. If we put all of this together, then we can write something like this:

struct ContentView: View {
enum Sheet: String, Identifiable {
case addItem, settings
var id: String { rawValue }
}
@State private var sheet: Sheet?
var body: some View {
VStack {
Button("Add Item", action: { sheet = .addItem })
Button("Settings", action: { sheet = .settings })
}
.sheet(item: $sheet, content: makeSheet)
}
@ViewBuilder
func makeSheet(_ sheet: Sheet) -> some View {
switch sheet {
case .addItem:
AddItemView()
case .settings:
SettingsView()
}
}
}

In the example above, I also separated the sheet view creation by having a separate function with an argument of type ContentView.Sheet. Since the function returns views with different types, then it needs to be annotated with @ViewBuilder. All in all it is a pretty concise and gives a nice call site where we just assign a sheet identifier to the sheet property.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
Foundation iOS Swift

URL type properties for folders in iOS

I have a tiny Swift package, what I have been using for reading and writing data on disk. Data is written to a subfolder in the documents folder. Beginning with iOS 16 there is a new way how to create that URL. It is such a tiny addition to what should have been there a long time ago.

let url = try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
// vs
let url2 = URL.documentsDirectory
view raw URL.swift hosted with ❤ by GitHub

Here we can see that instead of a throwing function which has 4 parameters, we can replace it with a non-throwing type property. Finally! Not sure why it gives me so much happiness, maybe because I always forget the URL API whenever I need it.

static var applicationDirectory: URL
static var applicationSupportDirectory: URL
static var cachesDirectory: URL
static var desktoDirectory: URL
static var documentsDirectory: URL
static var downloadsDirectory: URL
static var homeDirectory: URL
static var libraryDirectory: URL
static var moviesDirectory: URL
static var musicDirectory: URL
static var picturesDirectory: URL
static var sharedPublicDirectory: URL
static var temporaryDirectory: URL
static var trashDirectory: URL
static var userDirectory: URL
view raw URL.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
Combine iOS Swift

Async-await support for Combine’s sink and map

Async-await in Swift is getting more popular as time goes by, but Combine publishers do not have built-in support for it currently. In this blog post, we’ll see how to expand some of the existing publishers.

Async-await supported sink

One case where I have encountered this is when I have wanted to call an async function in sink. Although I could wrap the call with Task within the sink subscriber, it gets unnecessary long if I need to do it in many places. Instead, we can just do it once and add an async-await supported sink subscriber.

extension Publisher where Self.Failure == Never {
func sink(receiveValue: @escaping ((Self.Output) async -> Void)) -> AnyCancellable {
sink { value in
Task {
await receiveValue(value)
}
}
}
}
// Allows writing sink without Task
$imageURL
.compactMap({ $0 })
.sink { [weak self] url in
await self?.processImageURL(url)
}
.store(in: &cancellables)
view raw ViewModel.swift hosted with ❤ by GitHub

Async-await supported map

The Combine framework has map and tryMap for supporting throwing functions, but is lacking something like tryAwaitMap for async throwing functions. Combine has a publisher named Future which supports performing asynchronous work and publishing a value. We can use this to wrap a Task with asynchronous work. Another publisher in Combine is flatMap what is used for turning one kind of publisher to a new kind of publisher. Therefore, we can combine these to turn a downstream publisher to a new publisher of type Future. The first tryAwaitMap below is for a case where the downstream publisher emits errors, and the second one is for the case where the downstream does not emit errors. We need to handle these separately since we need to tell Combine how error types are handled (non-throwing publisher has failure type set to Never).

extension Publisher {
public func tryAwaitMap<T>(_ transform: @escaping (Self.Output) async throws -> T) -> Publishers.FlatMap<Future<T, Error>, Self> {
flatMap { value in
Future { promise in
Task {
do {
let result = try await transform(value)
promise(.success(result))
}
catch {
promise(.failure(error))
}
}
}
}
}
public func tryAwaitMap<T>(_ transform: @escaping (Self.Output) async throws -> T) -> Publishers.FlatMap<Future<T, Error>, Publishers.SetFailureType<Self, Error>> {
// The same implementation but the returned publisher transforms failures with SetFailureType.
}
}
// Case 1: throwing downstream publisher
$imageURL
.tryMap({ try Self.validateURL($0) })
.tryAwaitMap({ try await ImageProcessor.process($0) })
.map({ Image(uiImage: $0) })
.sink(receiveCompletion: { print("completion: \($0)") },
receiveValue: { print($0) })
.store(in: &cancellables)
// Case 2: non-throwing downstream publisher
$imageURL
.compactMap({ $0 })
.tryAwaitMap({ try await ImageProcessor.process($0) })
.map({ Image(uiImage: $0) })
.sink(receiveCompletion: { print("completion: \($0)") },
receiveValue: { print($0) })
.store(in: &cancellables)
view raw ViewModel.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift Swift Package

Most visited blog posts in 2022

Time to take a look at most visited blog posts in 2022 in two categories: most visited posts written in 2022 and most visited posts in overall.

Top 10 written in 2022

Top 10 overall

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift

Reading GraphQL queries from URLRequest in network tests

Back in 2019 I wrote about Testing networking code with custom URLProtocol on iOS. Recently, I was using the same approach for setting up network mocking for GraphQL requests, but then I stumbled on something unexpected. If we create an URLRequest which has httpBody set, then inside the custom URLProtocol implementation, httpBody method returns nil, and we need to access httpBodyStream instead. When dealing with GraphQL requests, the GraphQL query is part of the data set to httpBody. Reading the data from httpBody property would be straight-forward, then httpBodyStream on the other hand returns an InputStream and this requires a bit of code. InputStreams can only be read once, therefore while inspecting the URLRequest inside the URL protocol, we need to make sure not to read it twice. This is a nature of how input streams work.

In the snippet below, we can see a Data extension which adds a new initializer which takes in an InputStream. The implementation opens the stream, closes it when initializer is exited, reads data 512 bytes at the time. The read function of the InputStream returns number of read bytes if successful, negative value on error and 0 when buffer end has been reached which means that there is noting more to read.

extension Data {
init(inputStream: InputStream) throws {
inputStream.open()
defer { inputStream.close() }
self.init()
let bufferSize = 512
var readBuffer = [UInt8](repeating: 0, count: bufferSize)
while inputStream.hasBytesAvailable {
let readBytes = inputStream.read(&readBuffer, maxLength: bufferSize)
switch readBytes {
case 0:
break
case ..<0:
throw inputStream.streamError!
default:
append(readBuffer, count: readBytes)
}
}
}
}

Let’s see how to use this extension and reading a GraphQL query from the URLRequest. In the example below we can see that the example GraphQL data is set to a dictionary with 3 keys: query, operationName, and variables. Therefore, we need to first turn the InputStream into Data and then decoding the data to a model type and after that reading the query. Since we know the query, we can proceed with writing network tests where the mocked response depends on the query.

// Example data set to URLRequest httpBody
// {
// "query": "query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name friends { name } } }",
// "operationName": "",
// "variables": { "episode": "JEDI" }
// }
struct Payload: Decodable {
let query: String
}
let outputData = try Data(inputStream: httpBodyStream)
let payload = try JSONDecoder().decode(Payload.self, from: outputData)
print(payload.query)
// Prints: query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name friends { name } } }
view raw Usage.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.