SwiftUI offers several different ways for us to create stacks of overlapping views that can be arranged along the Z axis, which in turn enables us to define various kinds of overlays and backgrounds for the views that we build. Let’s explore some of those built-in stacking methods and what sort of UIs that they enable us to create.ZStacksLike its name implies, SwiftUI’s ZStack type is the Z-axis equivalent of the horizontally-oriented HStack and the vertical VStack. When placing multiple views within a ZStack, they’re (by default) rendered back-to-front, with the first view being placed at the back. For example, here we’re creating a full-screen ContentView, which renders a gradient with a text stacked on top:<pre data-preview="full-screen-gradient">struct ContentView: View { var body: some View { ZStack { LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) .ignoresSafeArea() Text("Swift by Sundell") .foregroundColor(.white) .font(.title) } }}</pre>Tip: You can use the above code sample’s PREVIEW button to see what it’ll look like when rendered.The reason that the above ContentView is rendered across all of the available screen space is because a LinearGradient will always occupy as much space as possible by default, and since a any stack’s size defaults to the total size of its children, that leads to our ZStack being resized to occupy that same full-screen space.The background modifierHowever, sometimes we might not want a given background to stretch out to fill all available space, and while we <em>could</em> address that by applying various sizing modifiers to our background view, SwiftUI ships with a built-in tool that automatically resizes a given view’s background to perfectly fit its parent — the background modifier.Here’s how we could use that modifier to instead apply our LinearGradient background directly to our Text-based view, which makes that background take on the exact same size as our text itself (including its padding):<pre data-preview="gradient-matched-size">struct ContentView: View { var body: some View { Text("Swift by Sundell") .foregroundColor(.white) .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) ) }}</pre>The reason that the padding is included when calculating our background’s size in the above example is because we’re applying the padding modifier <em>before</em> adding our background. To learn more about that, check out <a href="https://www.swiftbysundell.com/questions/swiftui-modifier-order">“When does the order of SwiftUI modifiers matter, and why?”.One thing that’s important to point out, though, is that even though a view’s background does indeed get resized according to the parent view itself, there’s no form of clipping applied by default. So if we were to give our LinearGradient an explicit size that’s larger than its parent, then it’ll actually be rendered out of bounds (which we can clearly demonstrate by adding a border to our main Text-based view):<pre data-preview="gradient-out-of-bounds">struct ContentView: View { var body: some View { Text("Swift by Sundell") .foregroundColor(.white) .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) .frame(width: 300, height: 300) ) .border(Color.blue) }}</pre>There are multiple ways to apply clipping to a view, though, which would remove the above sort of out-of-bounds rendering. For example, we could use either the clipped or clipShape modifier to tell the view to apply a clipping mask to its bounds, or we could give our view rounded corners (which also introduces clipping) — like this:<pre data-preview="gradient-rounded-corners">struct ContentView: View { var body: some View { Text("Swift by Sundell") .foregroundColor(.white) .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) .frame(width: 300, height:…
Swift by Sundell
Q&A: When does the order of SwiftUI modifiers matter, and why? | Swift by Sundell
Weekly Swift articles, podcasts and tips by John Sundell
Sometimes, we might want to automatically retry an asynchronous operation that failed, for example in order to work around temporary network problems, or to re-establish some form of connection.Here we’re doing just that when using Apple’s <a href="https://www.swiftbysundell.com/discover/combine">Combine framework</a> to implement a network call, which we’ll retry up to 3 times before handling any error that was encountered:<pre class="splash">struct SettingsLoader { var url: URL var urlSession = URLSession.shared var decoder = JSONDecoder() func load() -> AnyPublisher<Settings, Error> { urlSession .dataTaskPublisher(for: url) .map(\.data) .decode(type: Settings.self, decoder: decoder) .retry(3) .eraseToAnyPublisher() }}</pre>Note that the above example will unconditionally retry our loading operation (up to 3 times) regardless of what kind of error that was thrown.But what if we wanted to implement something similar, but using <a href="https://www.swiftbysundell.com/discover/concurrency">Swift Concurrency</a> instead? While Combine’s Publisher protocol includes the above retry operator as a built-in API, neither of Swift’s new concurrency APIs offer something similar (at least not at the time of writing), so we’ll have to get creative!One really neat aspect of Swift’s new concurrency system, and async/await in particular, is that it enables us to mix various asynchronous calls with standard control flow constructs, such as if statements and for loops. So, one way to implement automatic retries for await-marked calls would be to place the asynchronous code that we want to run within a loop that iterates over a range, which in turn describes how many retries that we wish to perform — like this:<pre class="splash">struct SettingsLoader { var url: URL var urlSession = URLSession.shared var decoder = JSONDecoder() func load() async throws -> Settings { // Perform 3 attempts, and retry on any failure: for _ in 0..<3 { do { return try await performLoading() } catch { // This 'continue' statement isn't technically // required, but makes our intent more clear: continue } } // The final attempt (which throws its error if it fails): return try await performLoading() } private func performLoading() async throws -> Settings { let (data, _) = try await urlSession.data(from: url) return try decoder.decode(Settings.self, from: data) }}</pre>The above implementation works perfectly fine, but if we’re looking to add the same kind of retrying logic in multiple places throughout a project, then it might be worth moving that code into some form of utility that could be easily reused.One way to do just that would be to extend Swift’s Task type with a convenience API that lets us quickly create such auto-retrying tasks. Our actual logic can remain almost identical to what it was before, but we’ll parameterize the maximum number of retries, and we’ll also add support for cancellation as well:<pre class="splash">extension Task where Failure == Error { @discardableResult static func retrying( priority: TaskPriority? = nil, maxRetryCount: Int = 3, operation: @Sendable @escaping () async throws -> Success ) -> Task { Task(priority: priority) { for _ in 0..<maxRetryCount { try Task<Never, Never>.checkCancellation() do { return try await operation() } catch { continue } } try Task<Never, Never>.checkCancellation() return try await operation() } }}</pre>That’s already a really useful, and completely reusable implementation, but let’s take things one step further, shall we?When retrying asynchronous operations, it’s very common to want to add a bit of delay between each retry — perhaps in order to give an external system (such as a server) a chance to recover from some kind of error before we make another attempt at calling it. So let’s also add support for such delays, which can easily be done using the built-in Task.sleep API:<pre class="splash">extension Task where Failure == Error { @discardableResult static func retrying( priority: TaskPriority? = nil, maxRetryCount: Int = 3, retryDelay: TimeInterval…
Swift by Sundell
Discover Combine on Swift by Sundell
Discover how Apple’s Combine framework can be used to model increasingly complex asynchronous operations as reactive pipelines that emit values over time.
Managing an app’s memory is something that tends to be especially tricky to do within the context of asynchronous code, as various objects and values often need to be captured and retained over time in order for our asynchronous calls to be performed and handled.While Swift’s relatively new async/await syntax does make many kinds of asynchronous operations easier to write, it still requires us to be quite careful when it comes to managing the memory for the various tasks and objects that are involved in such asynchronous code.Implicit capturesOne interesting aspect of async/await (and the Task type that we need to use to wrap such code when <a href="https://www.swiftbysundell.com/articles/connecting-async-await-with-other-swift-code/#calling-async-functions-from-a-synchronous-context">calling it from a synchronous context</a>) is how objects and values often end up being <em>implicitly captured</em> while our asynchronous code is being executed.For example, let’s say that we’re working on a DocumentViewController, which downloads and displays a Document that was downloaded from a given URL. To make our download execute lazily when our view controller is about to be displayed to the user, we’re starting that operation within our view controller’s viewWillAppear method, and we’re then either rendering the downloaded document once available, or showing any error that was encountered — like this:<pre class="splash">class DocumentViewController: UIViewController { private let documentURL: URL private let urlSession: URLSession ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Task { do { let (data, _) = try await urlSession.data(from: documentURL) let decoder = JSONDecoder() let document = try decoder.decode(Document.self, from: data) renderDocument(document) } catch { showErrorView(for: error) } } } private func renderDocument(_ document: Document) { ... } private func showErrorView(for error: Error) { ... }}</pre>Now, if we just quickly look at the above code, it might not seem like there’s any object capturing going on whatsoever. After all, asynchronous capturing has traditionally only happened within <a href="https://www.swiftbysundell.com/articles/capturing-objects-in-swift-closures">escaping closures</a>, which in turn require us to always explicitly refer to self whenever we’re accessing a local property or method within such a closure (when self refers to a class instance, that is).So we might expect that if we start displaying our DocumentViewController, but then navigate away from it before its download has completed, that it’ll be successfully deallocated once no external code (such as its parent UINavigationController) maintains a strong reference to it. But that’s actually not the case.That’s because of the aforementioned <em>implicit capturing</em> that happens whenever we create a Task, or use await to wait for the result of an asynchronous call. Any object used within a Task will automatically be retained until that task has finished (or failed), including self whenever we’re referencing any of its members, like we’re doing above.In many cases, this behavior might not actually be a problem, and will likely not lead to any actual <em>memory leaks</em>, since all captured objects will eventually be released once their capturing task has completed. However, let’s say that we’re expecting the documents downloaded by our DocumentViewController to potentially be quite large, and that we wouldn’t want multiple view controllers (and their download operations) to remain in memory if the user quickly navigates between different screens.The classic way to address this sort of problem would be to perform a weak self capture, which is often accompanied by a guard let self expression within the capturing closure itself — in order to turn that weak reference into a strong one that can then be used within the closure’s code:<pre class="splash">class DocumentViewController: UIViewController { ... override func viewWillAppear(_ animated: Bool)…
Swift by Sundell
Connecting async/await to other Swift code | Swift by Sundell
Let’s explore a few ways to “bridge the gap” between the new world of async/await and other kinds of asynchronous Swift code.
One of the core strengths of Swift’s protocols is that they enable us to define shared interfaces that multiple types can conform to, which in turn lets us interact with those types in a very uniform way, without necessarily knowing what underlying type that we’re currently dealing with.For example, to clearly define an API that enables us persist a given instance onto disk, we might choose to use a protocol that looks something like this:<pre class="splash">protocol DiskWritable { func writeToDisk(at url: URL) throws}</pre>One advantage of defining commonly used APIs that way is that it helps us keep our code consistent, as we can now make any type that should be disk-writable conform to the above protocol, which then requires us to implement the exact same method for all such types.Another big advantage of Swift protocols is that they’re extendable, which makes it possible for us to define all sorts of convenience APIs for both our own protocols, as well as those that are defined externally — for example within the standard library, or within any framework that we’ve imported.When writing those kinds of convenience APIs, we might also want to mix the protocol that we’re currently extending with some functionality provided by <em>another</em> protocol. For example, let’s say that we wanted to provide a default implementation of our DiskWritable protocol’s writeToDisk method for types that also conform to the Encodable protocol — since a type that’s encodable can be transformed into Data, which we could then automatically write to disk.One way to make that happen would be to make our DiskWritable protocol <em>inherit</em> from Encodable, which in turn will require all conforming types to implement both of those two protocols’ requirements. We could then simply extend DiskWritable in order to add that default implementation of writeToDisk that we were looking to provide:<pre class="splash">protocol DiskWritable: Encodable { func writeToDisk(at url: URL) throws}extension DiskWritable { func writeToDisk(at url: URL) throws { let encoder = JSONEncoder() let data = try encoder.encode(self) try data.write(to: url) }}</pre>While powerful, the above approach does have a quite significant downside, in that we’ve now completely coupled our DiskWritable protocol with Encodable — meaning that we can no longer use that protocol by itself, without also requiring any conforming type to also fully implement Encodable, which might become problematic.Another, much more flexible approach would be to let DiskWritable remain a completely stand-alone protocol, and instead write a type-constrained extension that only adds our default writeToDisk implementation to types that <em>also</em> conform to Encodable separately — like this:<pre class="splash">extension DiskWritable where Self: Encodable { func writeToDisk(at url: URL) throws { let encoder = JSONEncoder() let data = try encoder.encode(self) try data.write(to: url) }}</pre>The tradeoff here is that the above approach does require each type that wants to leverage our default writeToDisk implementation to explicitly conform to both DiskWritable and Encodable, which might not be a big deal, but it could make it a bit harder to discover that default implementation — since it’s no longer automatically available on all DiskWritable-conforming types.One way to address that discoverability issue, though, could be to create a convenience type alias (using Swift’s protocol composition operator, &) that gives us an indication that DiskWritable and Encodable can be combined to unlock new functionality:<pre class="splash">typealias DiskWritableByEncoding = DiskWritable & Encodable</pre>When a type conforms to those two protocols (either using the above type alias, or completely separately), it’ll now get access to our default writeToDisk implementation (while still having the option to provide its own, custom implementation as well):<pre class="splash">struct TodoList: DiskWritableByEncoding { var name: String var items: [Item] ...}let list = TodoList(...)try…
A major part of the challenge of architecting UI-focused code bases tends to come down to deciding where to draw the line between the code that needs to interact with the platform’s various UI frameworks, versus code that’s completely within our own app’s domain of logic.That task might become especially tricky when working with SwiftUI, as so much of our UI-centric logic tends to wind up within our various View declarations, which in turn often makes such code really difficult to verify using unit tests.So, in this article, let’s take a look at how we could deal with that problem, and explore how to make UI-related logic fully testable — even when that logic is primarily used within SwiftUI-based views.Logic intertwined with views“You shouldn’t put <em>business logic</em> within your views”, is a piece of advice that’s often mentioned when discussing unit testing within the context of UI-based projects, such as iOS and Mac apps. However, in practice, that advice can sometimes be tricky to follow, as the most natural or intuitive place to put view-related logic is often within the views themselves.As an example, let’s say that we’re working on an app that contains the following SendMessageView. Although the actual message sending logic (and its associated networking) has already been abstracted using a MessageSender protocol, all of the UI-specific logic related to sending messages is currently embedded right within our view:<pre class="splash">struct SendMessageView: View { var sender: MessageSender @State private var message = "" @State private var isSending = false @State private var sendingError: Error? var body: some View { VStack { Text("Your message:") TextEditor(text: $message) Button(isSending ? "Sending..." : "Send") { isSending = true sendingError = nil Task { do { try await sender.sendMessage(message) message = "" } catch { sendingError = error } isSending = false } } .disabled(isSending || message.isEmpty) if let error = sendingError { Text(error.localizedDescription) .foregroundColor(.red) } } }}</pre>At first glance, the above might not look so bad. Our view isn’t <em>massive</em> by any stretch of the imagination, and the code is quite well-organized. However, unit testing that view’s logic would currently be incredibly difficult — as we’d have to find some way to spin up our view within our tests, then find its various UI controls (such as its “Send” button), and then figure out a way to trigger and observe those views ourselves.Because we have to remember that SwiftUI views aren’t actual, concrete representations of the UI that we’re drawing on-screen, which can then be controlled and inspected as we wish. Instead, they’re ephemeral descriptions of what we want our various views to look like, which the system then renders and manages on our behalf.So, although we <em>could</em> most likely find a way to unit test our SwiftUI views directly — ideally, we’ll probably want to verify our logic in a much more controlled, isolated environment.One way to create such an isolated environment would be to extract all of the logic that we’re looking to test out from our views, and into objects and functions that are under our complete control — for example by using a <a href="https://www.swiftbysundell.com/articles/different-flavors-of-view-models-in-swift">view model</a>. Here’s what such a view model could end up looking like if we were to move all of our message sending UI logic out from our SendMessageView:<pre class="splash">@MainActor class SendMessageViewModel: ObservableObject { @Published var message = "" @Published private(set) var errorText: String? var buttonTitle: String { isSending ? "Sending..." : "Send" } var isSendingDisabled: Bool { isSending || message.isEmpty } private let sender: MessageSender private var isSending = false init(sender: MessageSender) { self.sender = sender } func send() { guard !message.isEmpty else { return } guard !isSending else { return } isSending = true errorText = nil Task { do { try await sender.sendMessage(message) message…
Swift by Sundell
Different flavors of view models in Swift | Swift by Sundell
View models attempt to make it easier to write and maintain view-specific logic, by introducing dedicated types for it. This week, let’s take a look at a few different ways that various flavors of view models can be implemented in Swift.
Auto linking is a feature that embeds information in your binaries' at compile time which is then used at link time to automatically link your dependencies. This allows you to reduce the duplication of flags between the different phases of your (or your consumers') builds.For example, with this Objective-C file:<pre class="highlight">#include <Foundation/Foundation.h>int main() { NSLog(@"Hello, World!"); return 1;}</pre>Compiled with:<pre class="highlight">$ clang -fmodules -c foo.m -o foo.o</pre>You can then inspect the options added for use at link time:<pre class="highlight">$ otool -l foo.o | grep LC_LINKER_OPTION -A3 cmd LC_LINKER_OPTION cmdsize 40 count 2 string #1 -framework string #2 Foundation...</pre>Now when linking this binary you don't have to pass any extra flags to the linker to make sure you link <code class="language-plaintext highlighter-rouge">Foundation:<pre class="highlight">$ ld foo.o -syslibroot `xcrun --show-sdk-path`</pre>To compare, if you compile the binary without <code class="language-plaintext highlighter-rouge">-fmodules<a href="#fn:1">1</a>:<pre class="highlight">$ clang -c foo.m -o foo.o</pre>You don't get any <code class="language-plaintext highlighter-rouge">LC_LINKER_OPTIONs. Then when linking the binary with the same command as before, it fails with these errors:<pre class="highlight">$ ld foo.o -syslibroot `xcrun --show-sdk-path`Undefined symbols for architecture arm64: "_NSLog", referenced from: _main in foo.o "___CFConstantStringClassReference", referenced from: CFString in foo.old: symbol(s) not found for architecture arm64</pre>To make it succeed you must explicitly link <code class="language-plaintext highlighter-rouge">Foundation through an argument to your linker invocation:<pre class="highlight">$ ld foo.o -syslibroot `xcrun --show-sdk-path` -framework Foundation</pre>Auto linking is also applied when using module maps that use the <code class="language-plaintext highlighter-rouge">link directive. For example with this module map file:<pre class="highlight">// module.modulemapmodule foo { link "foo" link framework "Foundation"}</pre>That you include with in this source file:<pre class="highlight">@import foo;int main() { return 1;}</pre>And compile (with an include path to the <code class="language-plaintext highlighter-rouge">module.modulemap file):<pre class="highlight">$ clang -fmodules -c foo.m -o foo.o -I.</pre>The produced object depends on <code class="language-plaintext highlighter-rouge">foo and <code class="language-plaintext highlighter-rouge">Foundation. This can be useful for handwriting module map files for prebuilt libraries, and for quite a few other cases. You can read about this file format in <a href="https://clang.llvm.org/docs/Modules.html">the docs</a>.You can also see auto linking with Swift code:<pre class="highlight">print("Hello, World!")</pre>Compiled with:<pre class="highlight">$ swiftc foo.swift -o foo.o -emit-object</pre>You can see it requires the Swift standard libraries:<pre class="highlight">$ otool -l foo.o | grep LC_LINKER_OPTION -A3 cmd LC_LINKER_OPTION cmdsize 24 count 1 string #1 -lswiftCore...</pre>For Swift this is especially useful since there are some underlying libraries like <code class="language-plaintext highlighter-rouge">libswiftSwiftOnoneSupport.dylib that need to be linked, but should be treated as implementation details that Swift developers are never exposed to.In general, this is more than you'll ever need to know about auto linking. But there are some situations where you might want to force binaries to include <code class="language-plaintext highlighter-rouge">LC_LINKER_OPTIONs when they don't automatically. For example, if your build system builds without <code class="language-plaintext highlighter-rouge">-fmodules (like bazel and cmake by default) and for some reason you cannot enable it<a href="#fn:1">1</a>, or when you're distributing a library and don't want your consumers to have to worry about adding extra linker flags.There are 2 different ways you can explicitly…
When working on bazel build infrastructure, something I often need to do is reproduce an action outside of bazel's infrastructure in order to debug it further. This debugging often involves changing flags or swapping out the tool itself for a custom built version.In many cases updating your bazel configuration as normal should work well enough. But sometimes when you're iterating on things that invalidate a significant portion of your build it can be faster to work on things outside of bazel first, and then update your bazel configuration based on your discoveries. Another case where this is useful is if you want to benchmark a specific action by running it many times individually without the contention caused by bazel parallelizing other actions.Since bazel has a lot of infrastructure for keeping builds hermetic, there are a few steps you need to take to <em>roughly</em> reproduce what bazel is doing so your debugging is as close to what it runs as possible.1. Build and disable sandboxingIn order for bazel to setup your build environment (including your downloaded dependencies), and leave it intact for you to muck around with, you must run a normal build and also either disable sandboxing by passing --spawn_strategy=standalone, or make it leave the sandbox base around by passing --sandbox_debug.2. Grab your action's command lineOnce bazel has run and left its environment intact, you need to grab the command line being run for the action you want to debug. I find that passing bazel's -s flag (also known as <a href="https://bazel.build/reference/command-line-reference#flag--subcommands">--subcommands</a>) is the easiest way to do this. You just have to make sure that the action you're interested in actually runs. There are a few different ways you can force bazel to run an action:Invalidate the inputs for the action. Unlike other build systems touching input files isn't enough, I often add newlines or comments to files to force actions to re-run.Change the flags for a command line. For some actions such as C++ compiles, or native binary linking, there are easy command line flags you can pass to invalidate the actions, specifically things like --copt=-v and --linkopt=-v respectively. Another useful thing to know is bazel doesn't have any semantics around the contents of these flags, so if you need to invalidate the action a second time, you can often append the same option again, repeating it, to make bazel re-run it. For example --linkopt=-v --linkopt=-v, often the underlying tool won't care about the repetition. This works best when changing flags will only invalidate a small number of actions so you don't have to rebuild a ton of things before you get to the action you care about.Change flags on the specific target. If changing flags globally is too invasive for your build, you can often edit the copts attribute of the specific target you care about to invalid the action. Again passing -v is often a useful way to get it to re-run without changing semantics of the build.Make it fail. Change the inputs or flags to something that is invalid, then your bazel invocation will stop after hitting the action in question.Once you have forcibly re-run your action with -s, you should see some output like this (specifics will vary based on your platform and the action you're debugging):<pre class="highlight">SUBCOMMAND: # //some_target:some_target [action 'Compiling Swift module //some_target:some_target', configuration: 725a049b28caa74d2a9605a6748b603bdab9e977931b2d02c0bb07b9a06575b2, execution platform: //:macos_x86_64](cd /private/var/tmp/_bazel_ksmiley/751b7cfc481e6eb168e92ffcfb919baa/execroot/someworkspace && \ exec env - \ APPLE_SDK_PLATFORM=iPhoneSimulator \ APPLE_SDK_VERSION_OVERRIDE=15.2 \ XCODE_VERSION_OVERRIDE=13.2.1.13C100 \ bazel-out/darwin_x86_64-opt-exec-8F99CFCD-ST-41e1ca5c471d/bin/external/build_bazel_rules_swift/tools/worker/universal_worker swiftc @bazel-out/ios-sim_arm64-min12.0-applebin_ios-ios_sim_arm64-fastbuild-ST-40c63e007684/bin/some_target/some_target.swiftmodule…
Checking whether two objects or values are considered equal is definitely one of the most commonly performed operations in all of programming. So, in this article, let’s take a look at how Swift models the concept of equality, and how that model varies between <a href="https://www.swiftbysundell.com/basics/value-and-reference-types">value and reference types.One the most interesting aspects of Swift’s implementation of equality is that it’s all done in a very protocol-oriented way — meaning that any type can become equatable by conforming to the Equatable protocol, which can be done like this:<pre class="splash">struct Article: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { lhs.title == rhs.title && lhs.body == rhs.body } var title: String var body: String}</pre>The way that we conform to Equatable in the above example is by implementing an <em>overload</em> of the == operator, which accepts the two values to compare (lhs, the left-hand side value, and rhs, the right-hand side value), and it then returns a boolean result as to whether those two values should be considered equal.The good news, though, is that we typically don’t have to write those kinds of == operator overloads ourselves, since the compiler is able to automatically synthesize such implementations whenever a type’s stored properties are all Equatable themselves. So in the case of the above Article type, we can actually remove our manual equality checking code, and simply make that type look like this:<pre class="splash">struct Article: Equatable { var title: String var body: String}</pre>The fact that Swift’s equality checks are so protocol-oriented also gives us a ton of power when working with <a href="https://www.swiftbysundell.com/basics/generics">generic types. For example, a collection of Equatable-conforming values (such as an Array or Set) are automatically considered equatable as well — without requiring any additional code on our part:<pre class="splash">let latestArticles = [ Article( title: "Writing testable code when using SwiftUI", body: "..." ), Article(title: "Combining protocols in Swift", body: "...")]let basicsArticles = [ Article(title: "Loops", body: "..."), Article(title: "Availability checks", body: "...")]if latestArticles == basicsArticles { ...}</pre>The way that those kinds of collection equality checks work is through Swift’s <em>conditional conformances</em> feature, which enables a type to conform to a specific protocol only when certain conditions are met. For example, here’s how Swift’s Array type conforms to Equatable only when the elements that are being stored within a given array are also, in turn, Equatable-conforming — which is what makes it possible for us to check whether two Article arrays are considered equal:<pre class="splash">extension Array where Element: Equatable { ...}</pre>Since none of the above logic is hard-coded into the compiler itself, we can also utilize that exact same conditional conformances-based technique if we wanted to make our <em>own generic types</em> conditionally equatable as well. For example, our code base might include some form of Group type that can be used to label a group of related values:<pre class="splash">struct Group<Value> { var label: String var values: [Value]}</pre>To make that Group type conform to Equatable when it’s being used to store Equatable values, we simply have to write the following empty extension, which looks almost identical to the Array extension that we took a look at above:<pre class="splash">extension Group: Equatable where Value: Equatable {}</pre>With the above in place, we can now check whether two Article-based Group values are equal, just like we could when using arrays:<pre class="splash">let latestArticles = Group( label: "Latest", values: [ Article( title: "Writing testable code when using SwiftUI", body: "..." ), Article(title: "Combining protocols in Swift", body: "...") ])let basicsArticles = Group( label: "Basics", values: [ Article(title: "Loops", body: "..."), Article(title: "Availability…
Swift by Sundell
Value and Reference Types | Swift by Sundell
Swift types can, in general, be divided into two categories — value types and reference types — which determines how they will be passed between different functions and other code scopes. Let’s take a look at what some of the practical implications of that…
My thanks to Judo for sponsoring Swift by Sundell — both the website and the podcast — during this first quarter of the year. Those of you who have been following my work for a while might know that I’m a big fan of the concept of “server-driven UIs”, where a UI fetches its entire view configuration from a remote server, rather than just downloading its data models.While I don’t recommend building an entire app that way — for certain kinds of screens, such as onboarding or payment flows, home screens or feeds, or other kinds of dynamic, content-driven views — being able to instantly change the way a given view is presented can be incredibly powerful. It can help make an app feel much more dynamic and up-to-date, while also letting teams iterate much quicker on such an app’s UI.However, building all of the infrastructure required to actually implement fully native, server-driven UIs is often incredibly complicated and time-consuming. That’s why, so far, we’ve mostly seen big tech companies adopt that kind of UI paradigm — but now, thanks to Judo, any team can easily start using server-driven UIs within their apps.Judo essentially consists of two parts. First, you’ve got a really nice, fully native Mac app that lets you build UIs completely visually — think Interface Builder or the SwiftUI canvas, but with a lot more power and flexibility:Judo Mac app screenshotThen, Judo also provides SDKs for both iOS and Android that let you dynamically load and configure the experiences you’ve built using that Mac app directly within your mobile app.That means that you get to decide exactly when and where you want to deploy your Judo-based views, and those views can then seamlessly interact with the rest of your app. Your users won’t even be able to tell the difference between the views that you’ve built from scratch and those that are powered by Judo — since all views remain completely native.Once those views are in place, you’re then free to iterate on them however you wish, and you can instantly deploy your changes server-side, without requiring you to submit a new binary to the App Store.If this sounds interesting to you, then head over to judo.app/sundell to try Judo completely for free, and to help support Swift by Sundell in the process.
via Swift by Sundell https://ift.tt/xzX4If0
via Swift by Sundell https://ift.tt/xzX4If0
In object-oriented programming, an <em>abstract</em> type provides a base implementation that other types can inherit from in order to gain access to some kind of shared, common functionality. What separates abstract types from regular ones is that they’re never meant to be used as-is (in fact, some programming languages even prevent abstract types from being instantiated directly), since their sole purpose is to act as a common parent for a group of related types.For example, let’s say that we wanted to unify the way we load certain types of models over the network, by providing a shared API that we’ll be able to use to separate concerns, to facilitate <a href="https://www.swiftbysundell.com/articles/different-flavors-of-dependency-injection-in-swift">dependency injection</a> and <a href="https://www.swiftbysundell.com/articles/mocking-in-swift">mocking</a>, and to keep method names consistent throughout our project.One <em>abstract type-based way</em> to do that would be to use a base class that’ll act as that shared, unified interface for all of our model-loading types. Since we don’t want that class to ever be used directly, we’ll make it trigger a fatalError if its base implementation is ever called by mistake:<pre class="splash">class Loadable<Model> { func load(from url: URL) async throws -> Model { fatalError("load(from:) has not been implemented") }}</pre>Then, each Loadable subclass will override the above load method in order to provide its loading functionality — like this:<pre class="splash">class UserLoader: Loadable<User> { override func load(from url: URL) async throws -> User { ... }}</pre>If the above sort of pattern looks familiar, it’s probably because it’s essentially the exact same sort of polymorphism that we typically <a href="https://www.swiftbysundell.com/basics/protocols">use protocols for in Swift</a>. That is, when we want to define an interface, a <em>contract</em>, that multiple types can conform to through distinct implementations.Protocols do have a significant advantage over abstract classes, though, in that the compiler will enforce that all of their requirements are properly implemented — meaning we no longer have to rely on runtime errors (such as fatalError) to guard against improper use, since there’s no way to instantiate a protocol by itself.So here’s what our Loadable and UserLoader types from before could look like if we were to go the protocol-oriented route, rather than using an abstract base class:<pre class="splash">protocol Loadable { associatedtype Model func load(from url: URL) async throws -> Model}class UserLoader: Loadable { func load(from url: URL) async throws -> User { ... }}</pre>Note how we’re now using an associated type to enable each Loadable implementation to decide what exact Model that it wants to load — which gives us a nice mix between full type safety and great flexibility.So, in general, protocols are definitely the preferred way to declare abstract types in Swift, but that doesn’t mean that they’re perfect. In fact, our protocol-based Loadable implementation currently has two main drawbacks:First, since we had to add an associated type to our protocol in order to keep our setup generic and type-safe, that means that Loadable can <a href="https://www.swiftbysundell.com/questions/referencing-generic-protocols">no longer be referenced directly</a>.And second, since protocols can’t contain any form of storage, if we wanted to add any stored properties that all Loadable implementations could make use of, we’d have to re-declare those properties within every single one of those concrete implementations.That property storage aspect is really a huge advantage of our previous, abstract class-based setup. So if we were to revert Loadable back to a class, then we’d be able to store all objects that our subclasses would need right within our base class itself — removing the need to duplicate those properties across multiple types:<pre class="splash">class Loadable<Model> { let networking: Networking let cache: Cache<URL…
Swift by Sundell
Different flavors of dependency injection in Swift | Swift by Sundell
Just like with most programming techniques, there are multiple
Swift’s type inference capabilities have been a very core part of the language since the very beginning, and heavily reduces the need for us to manually specify types when declaring variables and properties that have default values. For example, the expression var number = 7 doesn’t need to include any type annotations, since the compiler is able to infer that the value 7 is an Int and that our number variable should be typed accordingly.Swift 5.6, which was released as part of Xcode 13.3, continues to expand these type inference capabilities by introducing the concept of “type placeholders”, which can come very much in handy when working with collections and other generic types.For example, let’s say that we wanted to create an instance of Combine’s <a href="https://www.swiftbysundell.com/articles/using-combine-futures-and-subjects">CurrentValueSubject with a default integer value. An initial idea on how to do just that might be to simply pass our default value to that subject’s initializer, and then store the result in a local let (just like when creating a plain Int value). However, doing so gives us the following compiler error:<pre class="splash">// Error: "Generic parameter 'Failure' could not be inferred"let counterSubject = CurrentValueSubject(0)</pre>That’s because CurrentValueSubject is a generic type that needs to be specialized with not just an Output type, but also with a Failure type — which is the type of error that the subject is capable of throwing.Since we don’t want our subject to throw any errors in this case, we’ll give it the failure type Never (which is a common convention when using both Combine and Swift concurrency). But in order to do that, prior to Swift 5.6, we would need to explicitly specify our Int output type as well — like this:<pre class="splash">let counterSubject = CurrentValueSubject<Int, Never>(0)</pre>Starting in Swift 5.6, though, that’s no longer the case — as we can now use a <em>type placeholder</em> for our subject’s output type, which lets us once again leverage the compiler to automatically infer that type for us, just like when declaring a plain Int value:<pre class="splash">let counterSubject = CurrentValueSubject<_, Never>(0)</pre>That’s nice, but it’s arguably not the biggest improvement in the world. After all, we’re only saving two characters by replacing Int with _, and it’s not like manually specifying a simple type like Int was something problematic to begin with.But let’s now take a look at how this feature scales to more complex types, which is where it really starts to shine. For example, let’s say that our project contains the following function that lets us load a user-annotated PDF file:<pre class="splash">func loadAnnotatedPDF(named: String) -> Resource<PDF<UserAnnotations>> { ...}</pre>The above function uses a quite complex generic as its return type, which might be because we’re reusing our Resource type across multiple domains, and because we’ve opted to use <em>phantom types</em> to specify what kind of PDF that we’re currently dealing with.Now let’s take a look at what our CurrentValueSubject-based code from before would look like if we were to call the above function when creating our subject, rather than just using a simple integer:<pre class="splash">// Before Swift 5.6:let pdfSubject = CurrentValueSubject<Resource<PDF<UserAnnotations>>, Never>( loadAnnotatedPDF(named: name))// Swift 5.6:let pdfSubject = CurrentValueSubject<_, Never>( loadAnnotatedPDF(named: name))</pre>That’s quite a huge improvement! Not only does the Swift 5.6-based version save us some typing, but since the type of pdfSubject is now derived completely from the loadAnnotatedPDF function, that’ll likely make iterating on that function (and its related code) much easier — since there will be fewer manual type annotations that will need to be updated if we ever change that function’s return type.It’s worth pointing out, though, that there’s another way to leverage Swift’s type inference capabilities in situations like the one above —…
Swift by Sundell
Using Combine’s futures and subjects | Swift by Sundell
How Combine’s Future and subject types enable us to build many different kinds of functionality without having to write custom publisher implementations from scratch.
Today it’s been exactly five years since this website first launched, and what a journey it has been! When I started this website after having published weekly articles on Medium for a few months, I honestly had no idea what to expect. I wasn’t even sure if anyone would even find the site to begin with, let alone be interested in coming back week after week to read my new articles.But now, five years later, and with over 550 articles and 115 podcast episodes published, this website has been visited by over a million Swift developers from all around the world, and hundreds of thousands of people come back to read new articles and listen to new podcast episodes every single month. To see this website grow like that has been absolutely incredible, and I’m eternally grateful for all of the amazing support that you — the Swift community — have given me over these first five years.During these first few years, the amount of time that I’ve been able to spend working on my articles and podcast episodes has varied quite a lot. In the beginning, I was simply working on Swift by Sundell during my spare time, as a hobby project, and I had no plans or intentions to ever scale the project much beyond that. But at the same time, the website kept growing, and so did my ambitions, and at the start of 2019 (two years after the initial launch) I made the rather bold decision to turn Swift by Sundell into my full-time job.In retrospect, I’m incredibly happy that I made that decision, not only because it enabled me to produce a much larger number of more detailed articles, and spend more time on podcast editing to make each episode sound great — but also because it led to the creation of <a href="https://github.com/johnsundell/ink">Ink</a>, <a href="https://github.com/johnsundell/plot">Plot</a>, and <a href="https://github.com/johnsundell/publish">Publish</a>, and the current iteration of this website which uses those tools to produce a fast, easy to use, statically generated site that I’m super proud of.Working full-time on your own projects, as amazing as it in many ways is, also comes with quite a few downsides, though. In particular, I really started to miss working with other people, to be a part of a team, and to do actual iOS and macOS app development. Building Publish and my other open source tools was a lot of fun, and involved solving a ton of interesting problems, but it just wasn’t the same as working on complex app projects with dedicated co-workers.So in 2020 I decided to start freelancing again, part-time, while still keeping Swift by Sundell as my main, full-time job — which worked out great. In fact, it worked out so great that I started taking on more freelancing work throughout 2021, and for a while, I was incredibly happy with how my work was progressing. Business was great, with income from both this website’s sponsorships and from my freelancing work, I got to work on some incredibly cool projects, plus those projects gave me such a huge amount of inspiration for both my articles and podcast episodes. It all turned out to be one big, positive feedback loop.However, towards the end of 2021, I started to feel that something was wrong. I very often felt incredibly stressed, and sometimes even deeply disappointed in myself for not writing enough articles, for not publishing podcast episodes frequently enough, for not reviewing open source pull requests fast enough, or for not working enough hours on a given project.Looking back, it’s quite obvious what had happened. As I kept constantly expanding all of my projects and client work over time, I had ended up with more or less two full-time jobs, and it became clear that I was at the edge (or, some may say, even past the edge) of being burned out. Something had to change.So, at the beginning of this year, I sat down and started making a plan. I knew that I didn’t want to stop doing client work (I still work on some really nice projects with incredible teams), and I also knew that I didn’t want to quit working on this website, the…
GitHub
GitHub - JohnSundell/Ink: A fast and flexible Markdown parser written in Swift.
A fast and flexible Markdown parser written in Swift. - JohnSundell/Ink
Ever since its original introduction in 2019, SwiftUI has had really strong <a href="https://www.swiftbysundell.com/articles/swiftui-and-uikit-interoperability-part-1">interoperability with UIKit</a>. Both UIView and UIViewController instances can be wrapped to become fully SwiftUI-compatible, and UIHostingController lets us render any SwiftUI view within a UIKit-based view controller.However, even though macOS has had an NSHostingView for inlining SwiftUI views directly within any NSView (no view controller required) since the very beginning, there has never been a built-in way to do the same thing on iOS. Sure, we could always grab the underlying view from a UIHostingController instance, or add our hosting controller to a parent view controller in order to be able to use its view deeper within a UIView-based hierarchy — but neither of those solutions have ever felt entirely smooth.That brings us to iOS 16 (which is currently in beta at the time of writing). Although Apple haven’t introduced a fully general-purpose UIHostingView as part of this release, they <em>have</em> addressed one of the most common challenges that the lack of such a view presents us with — which is how to render a SwiftUI view within either a UITableViewCell or UICollectionViewCell.Just a quick note before we begin: All of this article’s code samples will be entirely UITableView-based, but the exact same techniques can also be used with UICollectionView as well.Say hello to UIHostingConfigurationIn iOS 14, Apple introduced a new way to configure cells that are being rendered as part of a UITableView or UICollectionView, which lets us use so-called <em>content configurations</em> to decouple the content that we’re rendering from the actual subviews that our cells contain. This API has now been extended with a new UIHostingConfiguration type, which lets us define a cell’s content using any SwiftUI view hierarchy.So wherever we’re configuring our cells — for example using the good-old-fashioned UITableViewDataSource dequeuing method — we can now opt to assign such a hosting configuration to a given cell’s contentConfiguration property, and UIKit will automatically render the SwiftUI views that we provide within that cell:<pre class="splash">class ListViewController: UIViewController, UITableViewDataSource { private var items: [Item] private let databaseController: DatabaseController private let cellReuseID = "cell" private lazy var tableView = UITableView() ... func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell( withIdentifier: cellReuseID, for: indexPath ) let item = items[indexPath.row] cell.contentConfiguration = UIHostingConfiguration { HStack { Text(item.title) Spacer() Button(action: { [weak self] in if let indexPath = self?.tableView.indexPath(for: cell) { self?.toggleFavoriteStatusForItem(atIndexPath: indexPath) } }, label: { Image(systemName: "star" + (item.isFavorite ? ".fill" : "")) .foregroundColor(item.isFavorite ? .yellow : .gray) }) } } return cell } private func toggleFavoriteStatusForItem(atIndexPath indexPath: IndexPath) { items[indexPath.row].isFavorite.toggle() databaseController.update(items[indexPath.row]) }}</pre>Neat! As the above code sample shows, we can easily send events back from our cell’s SwiftUI views to our view controller using closures, but there are a few things that are good to keep in mind when doing so.First, you might’ve noticed that we’re asking the table view for our cell’s current index path before passing it to our toggleFavoriteStatusForItem method. That’s because, if our table view supports operations such as moves and deletions, our cell might be at a different index path once our favorite button is tapped — so if we were to capture and use the indexPath parameter that was passed into our cell configuration method, then we might accidentally end up updating the wrong model.Second, we have to remember that we still very much remain in the world of UIKit, which means that…
Swift by Sundell
SwiftUI and UIKit interoperability - Part 1 | Swift by Sundell
A closer look at how SwiftUI and UIKit can be combined in various ways, starting with how we can bring increasingly complex UIKit-based views and view controllers into the declarative world of SwiftUI.
Swift 5.7 introduces a new, more concise way to unwrap optional values using if let and guard let statements. Before, we always had to explicitly name each unwrapped value, for example like this:
via Swift by Sundell https://ift.tt/WdxEJQV
class AnimationController { var animation: Animation? func update() { if let animation = animation { // Perform updates ... } }}But now, we can simply drop the assignment after our if let statement, and the Swift compiler will automatically unwrap our optional into a concrete value with the exact same name:class AnimationController { var animation: Animation? func update() { if let animation { // Perform updates ... } }}Neat! The above new syntax also works with guard statements:class AnimationController { var animation: Animation? func update() { guard let animation else { return } // Perform updates ... }}It can also be used to unwrap multiple optionals all at once:struct Message { var title: String? var body: String? var recipient: String? var isSendable: Bool { guard let title, let body, let recipient else { return false } return ![title, body, recipient].contains(where: \.isEmpty) }}A small, but very welcome feature. Of course, we still have the option to explicitly name each unwrapped optional if we wish to do so — either for code style reasons, or if we want to give a certain optional a different name when unwrapped.via Swift by Sundell https://ift.tt/WdxEJQV
Swift by Sundell
Swift 5.7’s new optional unwrapping syntax | Swift by Sundell
A quick look at a new, more concise way to unwrap optional values that’s being introduced in Swift 5.7.
Combining Swift’s flexible generics system with protocol-oriented programming can often lead to some really powerful implementations, all while minimizing code duplication and enabling us to establish clearly defined levels of abstraction across our code bases. However, when writing that sort of code before Swift 5.7, it’s been very common to run into the following compiler error:<pre>Protocol 'X' can only be used as a generic constraint because ithas Self or associated type requirements.</pre>Let’s take a look at how Swift 5.7 (which is currently in beta as part of Xcode 14) introduces a few key new features that aim to make the above kind error a thing of the past.Opaque parameter typesLike we took a closer look at in the Q&A article <a href="https://www.swiftbysundell.com/questions/referencing-generic-protocols">“Why can’t certain protocols, like Equatable and Hashable, be referenced directly?”</a>, the reason why it’s so common to encounter the above compiler error when working with generic protocols is that as soon as a protocol defines an <em>associated type</em>, the compiler starts placing limitations on how that protocol can be referenced.For example, let’s say that we’re working on an app that deals with various kinds of groups, and to be able to reuse as much of our group handling code as possible, we’ve chosen to define our core Group type as a generic protocol that lets each implementing type define what kind of Item values that it contains:<pre class="splash">protocol Group { associatedtype Item var items: [Item] { get } var users: [User] { get }}</pre>Now, because of that associated Item type, we can’t reference our Group protocol directly — even within code that has nothing to do with a group’s items, such as this function that computes what names to display from a given group’s list of users:<pre class="splash">// Error: Protocol 'Group' can only be used as a generic constraint// because it has Self or associated type requirements.func namesOfUsers(addedTo group: Group) -> [String] { group.users.compactMap { user in isUserAnonymous(user) ? nil : user.name }}</pre>One way to solve the above problem when using Swift versions lower than 5.7 would be to make our namesOfUsers function generic, and to then do what the above error message tells us, and only use our Group protocol as a <a href="https://www.swiftbysundell.com/articles/using-generic-type-constraints-in-swift-4">generic type constraint</a> — like this:<pre class="splash">func namesOfUsers<T: Group>(addedTo group: T) -> [String] { group.users.compactMap { user in isUserAnonymous(user) ? nil : user.name }}</pre>There’s of course nothing wrong with that technique, but it does make our function declaration quite a bit more complicated compared to when working with non-generic protocols, or any other form of Swift type (including concrete generic types).Thankfully, this is a problem that Swift 5.7 neatly solves by expanding the some keyword (that was introduced back in Swift 5.1) to also be applicable to function arguments. So, just like how we can declare that a SwiftUI view returns some View from its body property, we can now make our namesOfUsers function accept some Group as its input:<pre class="splash">func namesOfUsers(addedTo group: some Group) -> [String] { group.users.compactMap { user in isUserAnonymous(user) ? nil : user.name }}</pre>Just like when using the some keyword to define <a href="https://www.swiftbysundell.com/articles/opaque-return-types-in-swift">opaque return types</a> (like we do when building SwiftUI views), the compiler will automatically infer what actual concrete type that’s passed to our function at each call site, without requiring us to write any extra code. Neat!Primary associated typesSometimes, though, we might want to add a few more requirements to a given parameter, rather than just requiring it to conform to a certain protocol. For example, let’s say that we’re now working on an app that lets our users bookmark their favorite articles, and that we’ve created a BookmarksController…
Swift by Sundell
Q&A: Why can’t certain protocols, like Equatable and Hashable, be referenced directly? | Swift by Sundell
Weekly Swift articles, podcasts and tips by John Sundell
SwiftUI’s various stacks are some of the framework’s most fundamental layout tools, and enable us to define groups of views that are aligned either horizontally, vertically, or stacked in terms of depth.When it comes to the horizontal and vertical variants (HStack and VStack), we might sometimes end up in a situation where we want to dynamically switch between the two. For example, let’s say that we’re building an app that contains the following LoginActionsView, which lets the user pick from a list of actions when logging in:<pre data-preview="loginActionsView">struct LoginActionsView: View { ... var body: some View { VStack { Button("Login") { ... } Button("Reset password") { ... } Button("Create account") { ... } } .buttonStyle(ActionButtonStyle()) }}struct ActionButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .fixedSize() .frame(maxWidth: .infinity) .padding() .foregroundColor(.white) .background(Color.blue) .cornerRadius(10) }}</pre>Above, we’re using the fixedSize modifier to prevent our button labels from getting truncated, which is something that we should only do if we’re sure that a given view’s content won’t ever be larger than the view itself. To learn more, check out part three of my <a href="https://www.swiftbysundell.com/articles/swiftui-layout-system-guide-part-3/#fixed-dimensions">guide to SwiftUI’s layout system</a>.Currently, our buttons are stacked vertically, and fill all of the available space on the horizontal axis (you can use the above code sample’s PREVIEW button to see what that looks like). While that looks great on iPhones that are in portrait orientation, let’s say that we instead wanted to use a horizontal stack when our UI is rendered in landscape mode.GeometryReader to the rescue?One way to do that would be to use a GeometryReader to measure the currently available space, and based on whether the width of that space is larger than its height, we render our content using either an HStack or a VStack.While we <em>could</em> definitely place that logic right within our LoginActionsView itself, chances are quite high that we’ll want to reuse that code at some point in the future, so let’s instead create a dedicated view that’ll perform our dynamic stack-switching logic as a stand-alone component.To make our code even more future-proof, we won’t hard-code what alignment or spacing that our two stack variants will use. Instead, let’s do what SwiftUI itself does, and parametrize those attributes while also assigning the same default values that the framework uses — like this:<pre class="splash">struct DynamicStack<Content: View>: View { var horizontalAlignment = HorizontalAlignment.center var verticalAlignment = VerticalAlignment.center var spacing: CGFloat? @ViewBuilder var content: () -> Content var body: some View { GeometryReader { proxy in Group { if proxy.size.width > proxy.size.height { HStack( alignment: verticalAlignment, spacing: spacing, content: content ) } else { VStack( alignment: horizontalAlignment, spacing: spacing, content: content ) } } } }}</pre>Since we made our new DynamicStack use the same kind of API that HStack and VStack use, we can now simply swap out our previous VStack for an instance of our new, custom stack within our LoginActionsView:<pre data-preview="geometryReader">struct LoginActionsView: View { ... var body: some View { DynamicStack { Button("Login") { ... } Button("Reset password") { ... } Button("Create account") { ... } } .buttonStyle(ActionButtonStyle()) }}</pre>Neat! However, like the above code sample’s PREVIEW shows, using a GeometeryReader to perform our dynamic stack switching does come with a quite significant downside, in that geometry readers always fill all of the available space on both the horizontal and vertical axis (in order to actually be able to measure that space). In our case, that means that our LoginActionsView will no longer just stretch out horizontally, but it’ll now also move to the top of the screen.While there are various ways…
Swift by Sundell
A guide to the SwiftUI layout system - Part 3 | Swift by Sundell
This week, we’ll wrap up the SwiftUI layout system series by taking a look at how we can customize the layout behaviors of our views, using tools like layout priorities and alignment guides.
Thanks a lot to Caio and Mike, the two developers behind Essential Developer, for sponsoring Swift by Sundell. Essential Developer was founded to help iOS developers accelerate their journeys towards becoming complete senior developers, and on July 18th, Caio and Mike will kick off the next edition of their iOS Architect Crash Course, which you can sign up for completely for free by going to essentialdeveloper.com/sundell.During that course, which consists of three online lectures, you’ll get to explore concepts like app architecture, maintaining and refactoring legacy code, and how to effectively utilize techniques like composition within iOS code bases. Those are all concepts that most senior developers are expected to be very familiar with, but can also be hard to explore and practice on your own. That’s why so many developers have found this course to be so incredibly valuable.You’ll also get to ask your questions during the lectures as well, and there’s even bonus mentorship sessions available. All of this for the fantastic price of… free!If this sounds interesting to you, then head over to the Essential Developer website to sign up for the iOS Architect Crash Course today. It’s held completely online, so you can attend from anywhere in the world. But don’t wait too long to sign up because the next edition (at the time of writing) already starts on July 18th.Hope you’ll enjoy the course, and thanks a lot to Caio and Mike for sponsoring Swift by Sundell, which helps me keep the website and the podcast free and accessible to everyone.
via Swift by Sundell https://ift.tt/o6PCbqV
via Swift by Sundell https://ift.tt/o6PCbqV
Ever since Swift was first introduced, it’s been very common to need to use <a href="https://www.swiftbysundell.com/articles/different-flavors-of-type-erasure-in-swift">type erasure when working with generic protocols — ones that either reference Self within their requirements, or make use of associated types.For example, in earlier versions of Swift, when using Apple’s <a href="https://www.swiftbysundell.com/discover/combine">Combine framework for reactive programming, every time we wanted to return a Publisher from a function or computed property, we had to first type-erase it by wrapping it within an AnyPublisher — like this:<pre class="splash">struct UserLoader { var urlSession = URLSession.shared var decoder = JSONDecoder() func loadUser(withID id: User.ID) -> AnyPublisher<User, Error> { urlSession .dataTaskPublisher(for: urlForLoadingUser(withID: id)) .map(\.data) .decode(type: User.self, decoder: decoder) .eraseToAnyPublisher() } private func urlForLoadingUser(withID id: User.ID) -> URL { ... }}</pre>The reason type erasure had to be used in situations like that is because simply declaring that our method returns something that conforms to the Publisher protocol wouldn’t give the compiler any information as to what kind of output or errors that the publisher emits.Of course, an alternative to type erasure would be to declare the actual, concrete type that the above method returns. But when using frameworks that rely heavily on generics (such as Combine and SwiftUI), we very often end up with really complex nested types that would be very cumbersome to declare manually.This is a problem that was partially addressed in Swift 5.1, which introduced the some keyword and the concept of <em>opaque return types</em>, which are very often used when building views using SwiftUI — as they let us leverage the compiler to infer what concrete View-conforming type that’s returned from a given view’s body:<pre class="splash">struct ArticleView: View { var article: Article var body: some View { ScrollView { VStack(alignment: .leading) { Text(article.title).font(.title) Text(article.text) } .padding() } }}</pre>While the above way of using the some keyword works great in the context of SwiftUI, when we’re essentially just passing a given value into the framework itself (after all, we’re never expected to access the body property ourselves), it wouldn’t work that well when defining APIs for our own use.For example, replacing the AnyPublisher return type with some Publisher (and removing the call to eraseToAnyPublisher) within our UserLoader from before would <em>technically work</em> in isolation, but would also make each call site unaware of what type of output that our publisher produces — as we’d be dealing with a completely opaque Publisher type that can’t access any of the protocol’s associated types:<pre class="splash">struct UserLoader { ... func loadUser(withID id: User.ID) -> some Publisher { urlSession .dataTaskPublisher(for: urlForLoadingUser(withID: id)) .map(\.data) .decode(type: User.self, decoder: decoder) } ...}UserLoader() .loadUser(withID: userID) .sink(receiveCompletion: { completion in ... }, receiveValue: { output in // We have no way of getting a compile-time guarantee // that the output argument here is in fact a User // value, so we'd have to use force-casting to turn // that argument into the right type: let user = output as! User ... }) .store(in: &cancellables)</pre>This is where Swift 5.7’s introduction of <em>primary associated types</em> comes in. If we take a look at the declaration of Combine’s Publisher protocol, we can see that it’s been updated to take advantage of this feature by declaring that its associated Output and Failure types are primary (by putting them in angle brackets right after the protocol’s name):<pre class="splash">protocol Publisher<Output, Failure> { associatedtype Output associatedtype Failure: Error ...}</pre>That in turn enables us to use the some keyword in a brand new way — by declaring what exact types that our return value…
Swift by Sundell
Different flavors of type erasure in Swift | Swift by Sundell
A situation that most Swift developers will encounter at one point or another is when some form of type erasure is needed to be able to reference a generic protocol. This week, let’s start by taking a look at what makes type erasure such an essential technique…
When building various kinds of scrollable UIs, it’s very common to want to observe the current scroll position (or <em>content offset</em>, as UIScrollView calls it) in order to trigger layout changes, load additional data when needed, or to perform other kinds of actions depending on what content that the user is currently viewing.However, when it comes to SwiftUI’s ScrollView, there’s currently (at the time of writing) no built-in way to perform such scrolling observations. While embedding a ScrollViewReader within a scroll view does enable us to <em>change</em> the scroll position in code, it strangely (especially given its name) doesn’t let us <em>read</em> the current content offset in any way.One way to solve that problem would be to utilize the rich capabilities of UIKit’s UIScrollView, which — thanks to its delete protocol and the scrollViewDidScroll method — provides an easy way to get notified whenever any kind of scrolling occurred. However, even though I’m normally a big fan of using UIViewRepresentable and the other <a href="https://www.swiftbysundell.com/articles/swiftui-and-uikit-interoperability-part-1">SwiftUI/UIKit interoperability mechanisms</a>, in this case, we’d have to write quite a bit of extra code to bridge the gap between the two frameworks.That’s mainly because — at least on iOS — we can only embed SwiftUI content within a UIHostingController, not within a self-managed UIView. So if we wanted to build a custom, observable version of ScrollView using UIScrollView, then we’d have to wrap that implementation in a view controller, and then manage the relationship between our UIHostingController and things like the keyboard, the scroll view’s content size, safe area insets, and so on. Not impossibly by any means, but still, a fair bit of additional work and complexity.So, let’s instead see if we can find a completely SwiftUI-native way to perform such content offset observations.Resolving frames using GeometryReaderOne thing that’s key to realize before we begin is that both UIScrollView and SwiftUI’s ScrollView perform their scrolling by offsetting a container that’s hosting our actual scrollable content. They then clip that container to their bounds to produce the illusion of the viewport moving. So if we can find a way to observe the <em>frame of that container</em>, then we’ll essentially have found a way to observe the scroll view’s content offset.That’s where our good old friend GeometryReader comes in (wouldn’t be a proper SwiftUI layout workaround without it, right?). While GeometryReader is mostly used to access the size of the view that it’s hosted in (or, more accurately, that view’s <em>proposed size</em>), it also has another neat trick up its sleeve — in that it can be asked to read the frame of the current view relative to a given coordinate system.To use that capability, let’s start by creating a PositionObservingView, which lets us bind a CGPoint value to the current position of that view relative to a CoordinateSpace that we’ll also pass in as an argument. Our new view will then embed a GeometryReader as a background (which will make that geometry reader take on the same size as the view itself) and will assign the resolved frame’s origin as our offset using a preference key — like this:<pre class="splash">struct PositionObservingView<Content: View>: View { var coordinateSpace: CoordinateSpace @Binding var position: CGPoint @ViewBuilder var content: () -> Content var body: some View { content() .background(GeometryReader { geometry in Color.clear.preference( key: PreferenceKey.self, value: geometry.frame(in: coordinateSpace).origin ) }) .onPreferenceChange(PreferenceKey.self) { position in self.position = position } }}</pre>To learn more about how the @ViewBuilder attribute can be used when building custom SwiftUI container views, <a href="https://www.swiftbysundell.com/tips/annotating-properties-with-result-builder-attributes">check out this article</a>.The reason we use SwiftUI’s preference system above is because our GeometryReader…
Swift by Sundell
SwiftUI and UIKit interoperability - Part 1 | Swift by Sundell
A closer look at how SwiftUI and UIKit can be combined in various ways, starting with how we can bring increasingly complex UIKit-based views and view controllers into the declarative world of SwiftUI.
One of the most interesting aspects of SwiftUI, at least from an architectural perspective, is how it essentially treats views as data. After all, a SwiftUI view isn’t a direct representation of the pixels that are being rendered on the screen, but rather a <em>description</em> of how a given piece of UI should work, look, and behave.That very data-driven approach gives us a ton of flexibility when it comes to how we structure our view code — to the point where one might even start to question what the difference actually is between defining a piece of UI as a view type, versus implementing that same code as a modifier instead.Take the following FeaturedLabel view as an example — it adds a leading star image to a given text, and also applies a specific foreground color and font to make that text stand out as being “featured”:<pre class="splash">struct FeaturedLabel: View { var text: String var body: some View { HStack { Image(systemName: "star") Text(text) } .foregroundColor(.orange) .font(.headline) }}</pre>While the above may look like a typical custom view, the exact same rendered UI could just as easily be achieved using a “modifier-like” View protocol extension instead — like this:<pre class="splash">extension View { func featured() -> some View { HStack { Image(systemName: "star") self } .foregroundColor(.orange) .font(.headline) }}</pre>Here’s what those two different solutions look like side-by-side when placed within an example ContentView:<pre class="splash">struct ContentView: View { var body: some View { VStack { // View-based version: FeaturedLabel(text: "Hello, world!") // Modifier-based version: Text("Hello, world!").featured() } }}</pre>One key difference between our two solutions, though, is that the latter can be applied to <em>any view</em>, while the former only enables us to create String-based featured labels. That’s something that we could address, though, by turning our FeaturedLabel into a custom container view that accepts any kind of View-conforming content, rather than just a plain string:<pre class="splash">struct FeaturedLabel<Content: View>: View { @ViewBuilder var content: () -> Content var body: some View { HStack { Image(systemName: "star") content() } .foregroundColor(.orange) .font(.headline) }}</pre>Above we’re adding the ViewBuilder attribute to our content closure in order to enable the full power of SwiftUI’s view building API to be used at each call site (which, for example, lets us use if and switch statements when building the content for each FeaturedLabel).We might still want to make it easy to initialize a FeaturedLabel instance with a string, though, rather than always having to pass a closure containing a Text view. Thankfully, that’s something that we can easily make possible using a type-constrained extension:<pre class="splash">extension FeaturedLabel where Content == Text { init(_ text: String) { self.init { Text(text) } }}</pre>Above we’re using an underscore to remove the external parameter label for text, to mimic the way SwiftUI’s own, built-in convenience APIs work for types like Button and NavigationLink.With those changes in place, both of our two solutions now have the exact same amount of flexibility, and can easily be used to create both text-based labels, as well as labels that render any kind of SwiftUI view that we want:<pre class="splash">struct ContentView: View { @State private var isToggleOn = false var body: some View { VStack { // Using texts: Group { // View-based version: FeaturedLabel("Hello, world!") // Modifier-based version: Text("Hello, world!").featured() } // Using toggles: Group { // View-based version: FeaturedLabel { Toggle("Toggle", isOn: $isToggleOn) } // Modifier-based version: Toggle("Toggle", isOn: $isToggleOn).featured() } } }}</pre>So at this point, we might <em>really</em> start to ask ourselves — what exactly is the difference between defining a piece of UI as a view versus a modifier? Is there really any practical differences at all, besides code style and structure?Well, what about…