www.swiftbysundell.com
Open in
urlscan Pro
2606:4700:20::681a:2ec
Public Scan
Submitted URL: http://www.swiftbysundell.com//
Effective URL: https://www.swiftbysundell.com//
Submission: On September 30 via api from US — Scanned from US
Effective URL: https://www.swiftbysundell.com//
Submission: On September 30 via api from US — Scanned from US
Form analysis
0 forms found in the DOMText Content
Articles, podcasts and news about Swift development, by John Sundell. * Articles * Basics * Podcast * Discover * Subscribe * About * Search * RSS feed * Categories * SWIFTUI * CONCURRENCY * GENERICS * UNIT TESTING RECENTLY PUBLISHED Show compact list * SWIFTUI VIEWS VERSUS MODIFIERS * swiftui Published on 27 Feb 2023 Discover page available: 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 description 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”: struct FeaturedLabel: View { var text: String var body: some View { HStack { Image(systemName: "star") Text(text) } .foregroundColor(.orange) .font(.headline) } } 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: extension View { func featured() -> some View { HStack { Image(systemName: "star") self } .foregroundColor(.orange) .font(.headline) } } Here’s what those two different solutions look like side-by-side when placed within an example ContentView: struct ContentView: View { var body: some View { VStack { // View-based version: FeaturedLabel(text: "Hello, world!") // Modifier-based version: Text("Hello, world!").featured() } } } One key difference between our two solutions, though, is that the latter can be applied to any view, 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: struct FeaturedLabel<Content: View>: View { @ViewBuilder var content: () -> Content var body: some View { HStack { Image(systemName: "star") content() } .foregroundColor(.orange) .font(.headline) } } 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: extension FeaturedLabel where Content == Text { init(_ text: String) { self.init { Text(text) } } } 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: 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() } } } } So at this point, we might really 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 state? Let’s say that we wanted to make our new featured labels automatically fade in when they first appear? That would require us to define something like a @State-marked opacity property that we’d then animate over using an onAppear closure — for example like this: struct FeaturedLabel<Content: View>: View { @ViewBuilder var content: () -> Content @State private var opacity = 0.0 var body: some View { HStack { Image(systemName: "star") content() } .foregroundColor(.orange) .font(.headline) .opacity(opacity) .onAppear { withAnimation { opacity = 1 } } } } At first, participating in the SwiftUI state management system might seem like something that only proper view types can do, but it turns out that modifiers have the exact same capability — as long as we define such a modifier as a proper ViewModifier-conforming type, rather than just using a View protocol extension: struct FeaturedModifier: ViewModifier { @State private var opacity = 0.0 func body(content: Content) -> some View { HStack { Image(systemName: "star") content } .foregroundColor(.orange) .font(.headline) .opacity(opacity) .onAppear { withAnimation { opacity = 1 } } } } With the above in place, we can now replace our previous featured method implementation with a call to add our new FeaturedModifier to the current view — and both of our two featured label approaches will once again have the exact same end result: extension View { func featured() -> some View { modifier(FeaturedModifier()) } } Also worth noting is that when wrapping our code within a ViewModifier type, that code is lazily evaluated when needed, rather than being executed up-front when the modifier is first added, which could make a difference performance-wise in certain situations. So, regardless of whether we want to change the styles or structure of a view, or introduce a new piece of state, it’s starting to become clear that SwiftUI views and modifiers have the exact same capabilities. But that brings us to the next question — if there are no practical differences between the two approaches, how do we choose between them? At least to me, it all comes down to the structure of the resulting view hierarchy. Although we were, technically, changing the view hierarchy when wrapping one of our featured labels within an HStack in order to add our star image, conceptually, that was more about styling than it was about structure. When applying the featured modifier to a view, its layout or placement within the view hierarchy didn’t really change in any meaningful way — it still just remained a single view with the exact same kind of overall layout, at least from a high-level perspective. That’s not always the case, though. So let’s take a look at another example which should illustrate the potential structural differences between views and modifiers a bit more clearly. Here we’ve written a SplitView container, which takes two views — one leading, and one trailing — and then renders them side-by-side with a divider between them, while also maximizing their frames so that they’ll end up splitting the available space equally: struct SplitView<Leading: View, Trailing: View>: View { @ViewBuilder var leading: () -> Leading @ViewBuilder var trailing: () -> Trailing var body: some View { HStack { prepareSubview(leading()) Divider() prepareSubview(trailing()) } } private func prepareSubview(_ view: some View) -> some View { view.frame(maxWidth: .infinity, maxHeight: .infinity) } } Just like before, we could definitely achieve the exact same result using a modifier-based approach instead — which could look like this: extension View { func split(with trailingView: some View) -> some View { HStack { maximize() Divider() trailingView.maximize() } } func maximize() -> some View { frame(maxWidth: .infinity, maxHeight: .infinity) } } However, if we once again put our two solutions next to each other within the same example ContentView, then we can start to see that this time the two approaches do look quite different in terms of structure and clarity: struct ContentView: View { var body: some View { VStack { // View-based version: SplitView(leading: { Text("Leading") }, trailing: { Text("Trailing") }) // Modifier-based version: Text("Leading").split(with: Text("Trailing")) } } } Looking at the view-based call site above, it’s very clear that our two texts are being wrapped within a container, and it’s also easy to tell which of those two texts will end up being the leading versus trailing view. That same thing can’t really be said for the modifier-based version this time, though, which really requires us to know that the view that we’re applying the modifier to will end up in the leading slot. Plus, we can’t really tell that those two texts will end up being wrapped in some kind of container at all. It looks more like we’re styling the leading label using the trailing label somehow, which really isn’t the case. While we could attempt to solve that clarity problem with more verbose API naming, the core issue would still remain — that the modifier-based version doesn’t properly show what the resulting view hierarchy will be in this case. So, in situations like the one above, when we’re wrapping multiple siblings within a parent container, opting for a view-based solution will often give us a much clearer end result. On the flip side, if all that we’re doing is applying a set of styles to a single view, then implementing that as either a “modifier-like” extension, or using a proper ViewModifier type, will most often be the way to go. And for everything in between — such as our earlier “featured label” example — it all really comes down to code style and personal preference as to which solution will be the best fit for each given project. Just look at how SwiftUI’s built-in API was designed — containers (such as HStack and VStack) are views, while styling APIs (such as padding and foregroundColor) are implemented as modifiers. So, if we follow that same approach as much as possible within our own projects, then we’ll likely end up with UI code that feels consistent and inline with SwiftUI itself. I hope that you found this article interesting and useful. Feel free to find me on Mastodon, or contact me via email, if you have any questions, comments, or feedback. Thanks for reading! * * Read more about swiftui AVOIDING SWIFTUI’S ANYVIEW Patterns, tips and techniques that can help us avoid using AnyView. * Another article about swiftui BUILDING AN ASYNCHRONOUS SWIFTUI BUTTON * Continue exploring swiftui RENDERING TEXTURED VIEWS WITH SWIFTUI Tiling, slicing and resizing background images. * OBSERVING THE CONTENT OFFSET OF A SWIFTUI SCROLLVIEW * swiftui Published on 30 Jan 2023 Discover page available: SwiftUI When building various kinds of scrollable UIs, it’s very common to want to observe the current scroll position (or content offset, 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 change the scroll position in code, it strangely (especially given its name) doesn’t let us read 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 delegate 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 SwiftUI/UIKit interoperability mechanisms, 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 GEOMETRYREADER One 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 frame of that container, 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 proposed size), 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: 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 } } } To learn more about how the @ViewBuilder attribute can be used when building custom SwiftUI container views, check out this article. The reason we use SwiftUI’s preference system above is because our GeometryReader will be invoked as part of the view updating process, and we’re not allowed to directly mutate our view’s state during that process. So, by using a preference instead, we can deliver our CGPoint values to our view in an asynchronous fashion, which then lets us assign those values to our position binding. Now all that we need to do is to implement the PreferenceKey type that’s used above, and we’ll be good to go: private extension PositionObservingView { struct PreferenceKey: SwiftUI.PreferenceKey { static var defaultValue: CGPoint { .zero } static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { // No-op } } } We don’t actually need to implement any kind of reduce algorithm above, since we’ll only have a single view delivering values using that preference key within any given hierarchy (since our implementation is entirely contained within our PositionObservingView). Alright, so now we have a view that’s capable of reading and observing its own position within a given coordinate system. Let’s now use that view to build a ScrollView wrapper that’ll let us accomplish our original goal — to be able to read the current content offset within such a scroll view. FROM POSITION TO CONTENT OFFSET Our new ScrollView wrapper will essentially have two responsibilities — one, it’ll need to convert the position of our inner PositionObservingView into the current scroll position (or content offset), and two, it’ll also need to define a CoordinateSpace that the inner view can use to resolve its position. Besides that, it’ll simply forward its configuration parameters to its underlying ScrollView, so that we can decide what axes we want each scroll view to operate on, and so that we can decide whether or not to display any scrolling indicators. The good news is that converting our inner view’s position into content offset is as easy as negating both the x and y components of those CGPoint values. That’s because, as discussed earlier, a scroll view’s content offset is essentially just the distance that the container has been moved relative to the scroll view’s bounds. So let’s go ahead and implement our custom scroll view, which we’ll name OffsetObservingScrollView (spelling out ContentOffset does feel a bit too verbose in this case): struct OffsetObservingScrollView<Content: View>: View { var axes: Axis.Set = [.vertical] var showsIndicators = true @Binding var offset: CGPoint @ViewBuilder var content: () -> Content // The name of our coordinate space doesn't have to be // stable between view updates (it just needs to be // consistent within this view), so we'll simply use a // plain UUID for it: private let coordinateSpaceName = UUID() var body: some View { ScrollView(axes, showsIndicators: showsIndicators) { PositionObservingView( coordinateSpace: .named(coordinateSpaceName), position: Binding( get: { offset }, set: { newOffset in offset = CGPoint( x: -newOffset.x, y: -newOffset.y ) } ), content: content ) } .coordinateSpace(name: coordinateSpaceName) } } Note how we’re able to create a completely custom Binding for our inner view’s position parameter, by defining a getter and setter using closures. That’s a great option in situations like the one above, when we want to transform a value before assigning it to another Binding. That’s it! We now have a drop-in replacement for SwiftUI’s built-in ScrollView which enables us to observe the current content offset — which we can then bind to any state property that we’d like, for example in order to change the layout of a header view, to report analytics events to our server, or to perform any other kind of scroll position-based operation. You can find a complete example that uses the above OffsetObservingScrollView in order to implement a collapsable header view right here. I hope that you found this article useful. If you have any questions, comments, or feedback, then feel free to contact me on Mastodon, or send me an email. Thanks for reading! * * SWIFTUI Get the most out of Apple’s new UI framework. * Listen to a podcast episode about swiftui 104: “THE MAGIC OF AUGMENTED REALITY” WITH SPECIAL GUEST ROXANA JULA * Listen to a podcast episode about swiftui 118: “WHAT’S NEW IN SWIFTUI IN IOS 16?” WITH SPECIAL GUEST NATALIA PANFEROVA * PODCAST: “THE EVOLUTION OF SWIFT” WITH SPECIAL GUEST NICK LOCKWOOD * swift evolution * language features Published on 19 Dec 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS On this final episode of 2022, Nick Lockwood returns to the show to discuss the overall evolution of Swift and its ecosystem of tools and libraries. How has Swift changed since its original introduction in 2014, how does it compare to other modern programming languages, and how might the language continue to evolve in 2023 and beyond? SPONSORS * Bitrise: Rock-solid continuous integration for your Swift projects. Go to bitrise.io/swift to get started for free. * NordVPN: Get an exclusive discount on NordVPN’s excellent VPN service, by going to nordvpn.com/sundell. They even have a 30-day money-back guarantee. LINKS * Nick on Mastodon * John on Mastodon * ShapeScript * Kotlin * Rust * NSProxy * Lisp * Macros in C and C++ * Reflection in Swift * PHP * The Result type * Classes vs structs * Swift’s API design guidelines * Swift Concurrency * Swift Async Algorithms * The Future of Foundation * Swift Collections * Swift Evolution proposal for function back deployment * Building editable lists with SwiftUI * The Swift features that power SwiftUI’s API * Publish (static site generation in Swift) * swift-sh by Max Howell * Swift Evolution proposal for adding macros to the language * Accessing a property wrapper’s enclosing instance * Intro and outro music by Dariusz Dziuk * * Read more about Swift’s language features CALLING INSTANCE METHODS AS STATIC FUNCTIONS * Another article about Swift’s language features HOW SWIFT 5.3 ENHANCES SWIFTUI’S DSL Reduced verbosity, control flow improvements, and more. * Continue exploring Swift’s language features OPTIONALS From various ways of unwrapping and handling optional values, to how they can be extended with new APIs. * PODCAST: “SWIFT CONCURRENCY IN PRACTICE” WITH SPECIAL GUEST BEN SCHEIRMAN * concurrency Published on 18 Nov 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS Ben Scheirman returns to the show to discuss how Swift’s built-in concurrency features, such as async/await and tasks, can be used in practice when building apps for Apple’s platforms. SPONSORS * Essential Developer: Join the iOS Architect Crash Course to accelerate your journey towards becoming a senior developer. It’s 100% free and held entirely online. * NordVPN: Get an exclusive discount on NordVPN’s excellent VPN service, by going to nordvpn.com/sundell. They even have a 30-day money-back guarantee. LINKS * Ben on Twitter * John on Twitter * NSScreencast * Combine Swift * The Nike SNKRS app * Discover concurrency * Episode with Doug Gregor about Swift concurrency * The delegate pattern * PromiseKit * RxSwift * Discover Combine * Implementing debouncing using Combine * Grand Central Dispatch (GCD) * Async sequences and streams * Retrofitting existing APIs with async/await support * Async properties * The Just publisher * The MainActor attribute * Point-Free’s Combine schedulers * Previous episode with Ben about UICollectionView * Connecting and merging Combine publishers * Tonal Therapy * Intro and outro music by Dariusz Dziuk * * CONCURRENCY Explore Swift’s built-in concurrency system. * Read more about concurrency CREATING COMBINE-COMPATIBLE VERSIONS OF ASYNC/AWAIT-BASED APIS * Another article about concurrency AUTOMATICALLY RETRYING AN ASYNCHRONOUS SWIFT TASK * COMBINING OPAQUE RETURN TYPES WITH PRIMARY ASSOCIATED TYPES * language features * protocols Published on 12 Nov 2022 Basics article available: Protocols Ever since Swift was first introduced, it’s been very common to need to use 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 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: 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 { ... } } 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 opaque return types, 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: struct ArticleView: View { var article: Article var body: some View { ScrollView { VStack(alignment: .leading) { Text(article.title).font(.title) Text(article.text) } .padding() } } } 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 technically work 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: 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) This is where Swift 5.7’s introduction of primary associated types 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): protocol Publisher<Output, Failure> { associatedtype Output associatedtype Failure: Error ... } That in turn enables us to use the some keyword in a brand new way — by declaring what exact types that our return value will use for each of the protocol’s primary associated types. So if we first update our UserLoader to use that new feature: struct UserLoader { ... func loadUser(withID id: User.ID) -> some Publisher<User, Error> { urlSession .dataTaskPublisher(for: urlForLoadingUser(withID: id)) .map(\.data) .decode(type: User.self, decoder: decoder) } ... } Then we’ll no longer be required to use force-casting at each call site — all while also avoiding any kind of manual type erasure, as the compiler will now retain full type safety all the way from our loadUser method to each of its call sites: UserLoader() .loadUser(withID: userID) .sink(receiveCompletion: { completion in ... }, receiveValue: { user in // We're now getting a properly typed User // value passed into this closure. ... }) .store(in: &cancellables) Of course, since primary associated types isn’t just a Combine-specific thing, but rather a proper Swift feature, we can also use the above pattern when working with our own generic protocols as well. For example, let’s say that we’ve defined a Loadable protocol that lets us abstract different ways of loading a given value behind a single, unified interface (this time using Swift concurrency): protocol Loadable<Value> { associatedtype Value func load() async throws -> Value } struct NetworkLoadable<Value: Decodable>: Loadable { var url: URL func load() async throws -> Value { // Load the value over the network ... } } struct DatabaseLoadable<Value: Identifiable>: Loadable { var id: Value.ID func load() async throws -> Value { // Load the value from the app's local database ... } } A big benefit of using a pattern like that is that it enables us to very neatly separate concerns, as each call site doesn’t have to be aware of exactly how a given value is loaded — we can simply return some Loadable from a given function, and thanks to our primary associated type, we get full type safety without having to reveal what underlying type that’s used to perform the actual loading: func loadableForArticle(withID id: Article.ID) -> some Loadable<Article> { let url = urlForLoadingArticle(withID: id) return NetworkLoadable(url: url) } However, one important limitation of opaque return types is that the compiler requires all code paths within a scope that returns an opaque type to always return the exact same type. So, if we wanted to dynamically switch between two different Loadable implementations, then we’d get a compiler error if we tried to keep using the some keyword like we did above: // Error: Function declares an opaque return type 'some Loadable<Article>', // but the return statements in its body do not have matching underlying types. func loadableForArticle(withID id: Article.ID) -> some Loadable<Article> { if useLocalData { return DatabaseLoadable(id: id) } let url = urlForLoadingArticle(withID: id) return NetworkLoadable(url: url) } One way to solve the above problem would be to use the good old fashioned approach of introducing a type-erasing AnyLoadable type, which we could use to wrap both of our underlying Loadable instances — but at this point, that does arguably feel like a step backwards, since we’d have to write that type-erased wrapper manually. Or do we? It turns out that we can, in fact, keep leveraging the compiler even in these kinds of more dynamic situations — all that we have to do is replace the some keyword with Swift’s new any keyword, and the compiler will actually perform all of the required type erasure on our behalf: func loadableForArticle(withID id: Article.ID) -> any Loadable<Article> { if useLocalData { return DatabaseLoadable(id: id) } let url = urlForLoadingArticle(withID: id) return NetworkLoadable(url: url) } Just like when using some in combination with primary associated types, using any retains full type-safety, and still enables us to use all available Loadable APIs, and maintain complete awareness that the returned instance loads Article values. Neat! It’s important to point out, though, that using the any keyword in the above kind of way turns our method’s return value into a so-called existential, which does come with a certain performance overhead, and might also prevent us from using certain generic APIs. For example, if we were to use the any keyword within the earlier Combine-based example, then we’d be locked out of applying any kind of operators (like map or flatMap) on the returned publisher. So, when possible, it’s definitely preferable to use the some keyword instead. I hope that you found this article useful. If you want to learn more about some and any, then check out my earlier article about those keywords, which focuses on how they can be used when declaring properties and parameter types. And if you have any questions, comments, or feedback, then feel free to reach out. Thanks for reading! * * Read more about Swift’s language features 5 SMALL BUT SIGNIFICANT IMPROVEMENTS IN SWIFT 5.1 * Another article about Swift’s language features ORGANIZING DEFAULT ARGUMENT VALUES * Continue exploring Swift’s language features USING KEY PATHS TO CREATE CONVENIENCE APIS * PODCAST: “RESPONSIVE AND SMOOTH UIS” WITH SPECIAL GUEST ADAM BELL * ui development * performance Published on 31 Oct 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS Adam Bell returns to the podcast to discuss different techniques and approaches for optimizing UI code, and how to utilize tools like animations in order to build iOS apps that feel fast and responsive. SPONSORS * NordVPN: Get an exclusive discount on NordVPN’s excellent VPN service, by going to nordvpn.com/sundell. They even have a 30-day money-back guarantee. * Bitrise: Rock-solid continuous integration for your Swift projects. Go to bitrise.io/swift to get started for free. LINKS * Adam on Twitter * John on Twitter * Motion * CloudKit * Debouncing * CATransaction * Grand Central Dispatch * UITraitCollection * SIMD * CAAnimation * Modifying a view’s transform * Snapshotting a UIView * CALayer * View controller transitions * Extracting the animation curve used for keyboard presentation * Adding spring parameters to a UIView animation * beginFromCurrentState animation option * Intro and outro music by Dariusz Dziuk * * Read more about ui development IMPORTING INTERACTIVE UIKIT VIEWS INTO SWIFTUI Why rewrite views when we can reuse them? * Another article about performance PICKING THE RIGHT DATA STRUCTURE IN SWIFT How different data structures can have a big impact on performance. * Continue exploring ui development RENDERING TEXTURED VIEWS WITH SWIFTUI Tiling, slicing and resizing background images. * PODCAST: “FREELANCING AND WWDC22 HIGHLIGHTS” WITH SPECIAL GUEST DONNY WALS * wwdc22 * language features * project management Published on 17 Sep 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS Donny Wals returns to the show to talk about being an iOS developer freelancer, and to discuss some of the key new APIs, Swift language features, and frameworks that were introduced at WWDC22. SPONSORS * Essential Developer: Join the iOS Architect Crash Course to accelerate your journey towards becoming a senior developer. It’s 100% free and held entirely online. * Bitrise: Rock-solid continuous integration for your Swift projects. Go to bitrise.io/swift to get started for free. LINKS * Donny on Twitter * John on Twitter * “What’s new in SwiftUI in iOS 16?”, with Natalia Panferova * SwiftUI’s new NavigationStack API * Building “desktop-class” iPad apps * Swift Charts * The SwiftUI Layout protocol * UIHostingConfiguration * NSUserActivity * WidgetKit * Live Activities * AsyncCompatibilityKit * Swift 5.7’s new optional unwrapping syntax * Intro and outro music by Dariusz Dziuk * * Another episode about Swift’s language features 103: “WHAT’S NEW IN SWIFT 5.5” WITH SPECIAL GUEST ANTOINE VAN DER LEE * Read more about Swift’s language features CONDITIONAL COMPILATION WITHIN SWIFT EXPRESSIONS * Another article about wwdc22 SWITCHING BETWEEN SWIFTUI’S HSTACK AND VSTACK * PODCAST: “THE ROLE OF SYSTEM DESIGN” WITH SPECIAL GUEST GUI RAMBO * architecture * project management Published on 31 Aug 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS Gui Rambo returns to the show to talk about the role and importance of system design when building apps and open source tools, and how common app architectures and design patterns can be augmented with custom systems. SPONSORS * NordVPN: Get an exclusive discount on NordVPN’s excellent VPN service, by going to nordvpn.com/sundell. They even have a 30-day money-back guarantee. * Bitrise: Rock-solid continuous integration for your Swift projects. Go to bitrise.io/swift to get started for free. LINKS * Rambo on Twitter * John on Twitter * Stacktrace * AirBuddy * ChibiStudio * MVVM * MVC * The factory pattern * The observer pattern * Combine * Swift Concurrency * Async streams * Diagrams * MindNode * VirtualBuddy * DocC * Sparkle * Intro and outro music by Dariusz Dziuk * * Read more about architecture ALTERNATIVES TO PROTOCOLS IN SWIFT Four different ways of defining abstractions in Swift. * Another article about architecture SPLITTING UP SWIFT TYPES Enabling composition by first decomposing larger types into smaller building blocks. * Watch a video about architecture THE DEVELOPER JOURNEY FROM PRAGMA CONFERENCE Preparing ourselves, and our code, for future changes. * ESSENTIAL DEVELOPER * Sponsored Published on 13 Jul 2022 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. * PODCAST: “WHAT’S NEW IN SWIFTUI IN IOS 16?” WITH SPECIAL GUEST NATALIA PANFEROVA * swiftui * uikit Published on 11 Jul 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS Natalia Panferova joins John to discuss some of the key new features that are coming to SwiftUI and UIKit in iOS 16, and to talk about her experience working on SwiftUI at Apple. SPONSORS * NordVPN: Get an exclusive discount on NordVPN’s excellent VPN service, by going to nordvpn.com/sundell. They even have a 30-day money-back guarantee. * Essential Developer: Join the iOS Architect Crash Course to accelerate your journey towards becoming a senior developer. The next edition starts on July 18th, and it’s 100% free and held entirely online. LINKS * Natalia on Twitter * John on Twitter * Natalia’s website: nilcoalescing.com * The Layout protocol * GeometryReader * AttributedString * Natalia’s article about the new SwiftUI navigation APIs * NavigationStack * NavigationSplitView * NavigationViewStyle * Programmatic navigation in SwiftUI (before iOS 16) * How to sync the width or height of two SwiftUI views? * AnyLayout * ViewThatFits * Natalia’s article about the new SwiftUI sheet APIs * Swift Charts * Rendering SwiftUI views within UITableView or UICollectionView cells on iOS 16 * Building modern collection views * SwiftUI/UIKit interoperability * Intro and outro music by Dariusz Dziuk * * Read more about swiftui BUILDING AN ASYNCHRONOUS SWIFTUI BUTTON * Another article about swiftui DEFINING DYNAMIC COLORS IN SWIFT Using either SwiftUI or UIKit. * Continue exploring swiftui OBSERVING COMBINE PUBLISHERS IN SWIFTUI VIEWS Lightweight state observations. * SWITCHING BETWEEN SWIFTUI’S HSTACK AND VSTACK * swiftui * layout * wwdc22 Published on 08 Jul 2022 Discover page available: SwiftUI 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: Preview 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) } } 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 guide to SwiftUI’s layout system. 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 could 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: 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 ) } } } } } 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: Preview struct LoginActionsView: View { ... var body: some View { DynamicStack { Button("Login") { ... } Button("Reset password") { ... } Button("Create account") { ... } } .buttonStyle(ActionButtonStyle()) } } 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 that we could address those problems (for example by using a technique similar to the one we used to make multiple views have the same width or height in this Q&A article), the question is really whether measuring the available space is really a good approach when it comes to determining the orientation of our dynamic stacks. A CASE FOR SIZE CLASSES Instead, let’s use Apple’s size class system to decide whether our DynamicStack should use an HStack or a VStack under the hood. The benefit of doing that is not just that we’ll be able to retain the same compact layout that we had before introducing a GeometryReader into the mix, but also that our DynamicStack will start behaving in a way that’s very similar to how built-in system components behave across all devices and orientations. To start observing the current horizontal size class, all we have to do is to use SwiftUI’s environment system — by declaring an @Environment-marked property (with the horizontalSizeClass key path) within our DynamicStack, which will then let us switch on the current sizeClass value within our view’s body: Preview struct DynamicStack<Content: View>: View { ... @Environment(\.horizontalSizeClass) private var sizeClass var body: some View { switch sizeClass { case .regular: hStack case .compact, .none: vStack @unknown default: vStack } } } private extension DynamicStack { var hStack: some View { HStack( alignment: verticalAlignment, spacing: spacing, content: content ) } var vStack: some View { VStack( alignment: horizontalAlignment, spacing: spacing, content: content ) } } With the above in place, our LoginActionsView will now dynamically switch between having a horizontal layout when rendered using the regular size class (for example in landscape on larger iPhones, or in either orientation when running full-screen on iPad), and a vertical layout when any other size class configuration is used. All while still using a compact vertical layout that doesn’t use any more space than what’s needed to render its content. USING THE LAYOUT PROTOCOL Although we’ve already ended up with a neat solution that works across all iOS versions that support SwiftUI, let’s also explore a few new layout tools that are being introduced in iOS 16 (which at the time of writing is still in beta as part of Xcode 14). One such tool is the new Layout protocol, which both enables us to build completely custom layouts that can be integrated directly into SwiftUI’s own layout system (more on that in a future article), while also providing us with a new way to dynamically switch between various layouts in a very smooth, full animatable way. That’s because it turns out that Layout is not just an API for us third-party developers, but Apple have also made SwiftUI’s own layout containers use that new protocol as well. So, rather than using HStack and VStack directly as container views, we can instead use them as Layout-conforming instances that are wrapped using the AnyLayout type — like this: private extension DynamicStack { var currentLayout: AnyLayout { switch sizeClass { case .regular, .none: return horizontalLayout case .compact: return verticalLayout @unknown default: return verticalLayout } } var horizontalLayout: AnyLayout { AnyLayout(HStack( alignment: verticalAlignment, spacing: spacing )) } var verticalLayout: AnyLayout { AnyLayout(VStack( alignment: horizontalAlignment, spacing: spacing )) } } The above works since both HStack and VStack directly conform to the new Layout protocol when their Content type is EmptyView (which is the case when we don’t pass any content closure to such a stack), as we can see if we take a peak at SwiftUI’s public interface: extension VStack: Layout where Content == EmptyView { ... } Note that, due to a regression, the above conditional conformance was omitted from Xcode 14 beta 3. According to Matt Ricketson from the SwiftUI team, a temporary workaround would be to instead use the underlying _HStackLayout and _VStackLayout types directly. Hopefully that regression will be fixed in future betas. Now that we’re able to resolve what layout to use through our new currentLayout property, we can now update our body implementation to simply call the AnyLayout that’s returned from that property as if it was a function — like this: struct DynamicStack<Content: View>: View { ... var body: some View { currentLayout(content) } } The reason that we can apply our layout by calling it as a function (even though it’s actually a struct) is because the Layout protocol uses Swift’s “call as function” feature. So what’s the difference between our previous solution and the above, Layout-based one? The key difference (besides the fact that the latter requires iOS 16) is that switching layouts preserves the identity of the underlying views that are being rendered, which isn’t the case when swapping between an HStack and a VStack. The result of that is that animations will be much smoother, for example when switching device orientations, and we’re also likely to get a small performance boost when performing such changes as well (since SwiftUI always performs best when its view hierarchies are as static as possible). PICKING THE VIEW THAT FITS But we’re not quite done yet, because iOS 16 also gives us another interesting new layout tool that could potentially be used to implement our DynamicStack — which is a new view type called ViewThatFits. Like its name implies, that new container will pick the view that best fits within the current context, based on a list of candidates that we pass when initializing it. In our case, that means that we could pass it both an HStack and a VStack, and it’ll automatically switch between them on our behalf: struct DynamicStack<Content: View>: View { ... var body: some View { ViewThatFits { HStack( alignment: verticalAlignment, spacing: spacing, content: content ) VStack( alignment: horizontalAlignment, spacing: spacing, content: content ) } } } Note that it’s important that we place the HStack first in this case, since the VStack will likely always fit, even within contexts where we want our layout to be horizontal (such as in full-screen mode on iPad). It’s also important to point out that the above ViewThatFits-based technique will always attempt to use our HStack, even when rendered with the compact size class, and will only pick our VStack-based layout when the HStack doesn’t fit. CONCLUSION So that’s four different ways to implement a dedicated DynamicStack view that dynamically switches between an HStack and a VStack depending on the current context. I hope you enjoyed this article, and if you have any questions, comments, or feedback, then feel free to reach out via either Twitter or email. Thanks for reading! * * SWIFTUI Get the most out of Apple’s new UI framework. * Listen to a podcast episode about swiftui 60: “DEPLOYING SWIFTUI IN PRODUCTION” WITH SPECIAL GUEST DAVID SMITH * Listen to a podcast episode about swiftui 87: “THE SWIFTUI LAYOUT SYSTEM” WITH SPECIAL GUEST CHRIS EIDHOF How the layout system works. * PODCAST: “SWIFT 5.7, GENERICS, AND THE ROAD TO SWIFT 6” WITH SPECIAL GUEST BEN COHEN * wwdc22 * apple interview * generics Published on 10 Jun 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS Ben Cohen, manager of the Swift team at Apple, joins John on this WWDC22 special to discuss Swift 5.7, how generics have been made more powerful and easy to use, and how the language is expected to evolve towards Swift 6. SPONSORS * Bitrise: Rock-solid continuous integration for your Swift projects. Go to bitrise.io/swift to get started for free. * NordVPN: Get an exclusive discount on NordVPN’s excellent VPN service, by going to nordvpn.com/sundell. They even have a 30-day money-back guarantee. LINKS * Ben on Twitter * John on Twitter * Swift Async Algorithms * Swift Collections * Using the new ‘some’ and ‘any’ keywords * Type erasure * “What’s New in Swift”, from WWDC22 * “Embracing Swift Generics”, from WWDC22 * WeatherKit * Regex Literals * RegexBuilder * Result builders * App Intents * Sourcery * Doug Gregor’s “Eliminate data races using Swift Concurrency” talk * Ben’s Swift concurrency talk from WWDC21 * Grand Central Dispatch * Swift Distributed Actors * Connecting async/await to other Swift code * The Swift Mentorship Program * Intro and outro music by Dariusz Dziuk * Article USING THE ‘SOME’ AND ‘ANY’ KEYWORDS TO REFERENCE GENERIC PROTOCOLS IN SWIFT 5.7 * wwdc22 * generics * protocols Published on 09 Jun 2022 How Swift 5.7 makes generic protocols more powerful and easier to work with, thanks to the ‘some’ and ‘any’ keywords. * Article SWIFT 5.7’S NEW OPTIONAL UNWRAPPING SYNTAX * wwdc22 * language features * optionals Published on 07 Jun 2022 A quick look at a new, more concise way to unwrap optional values that’s being introduced in Swift 5.7. * Article RENDERING SWIFTUI VIEWS WITHIN UITABLEVIEW OR UICOLLECTIONVIEW CELLS ON IOS 16 * wwdc22 * uikit * swiftui Published on 07 Jun 2022 Exploring how iOS 16’s new UIHostingConfiguration API enables us to inline SwiftUI views within our UITableView or UICollectionView cells. * Podcast episode 116: “THE EVOLUTION OF SWIFTUI” WITH SPECIAL GUEST CHRIS EIDHOF * swiftui * architecture Published on 18 May 2022 Chris Eidhof returns to the podcast to talk about how SwiftUI has evolved since its initial release, to share several key learnings from using it over the past few years, and to discuss concepts like app architecture and state management. * Special SWIFT BY SUNDELL TURNS FIVE YEARS OLD TODAY! HERE’S WHAT’S NEXT FOR THE WEBSITE AND THE PODCAST Published on 05 May 2022 Celebrating the fifth birthday of this website, while also sharing some important announcements about its future. * Podcast episode 115: “A FRAMEWORK AND AN APP” WITH SPECIAL GUEST SIMON STØVRING * developer tools * performance * maintenance Published on 30 Apr 2022 Simon Støvring returns to the show to talk about how he built his new text editor Runestone, how to effectively manage an app’s settings, performance tuning, and implementing an app’s core logic as a stand-alone framework. * Article TYPE PLACEHOLDERS IN SWIFT * language features * generics Published on 14 Apr 2022 New in Swift 5.6: We can now use type placeholders to select what generic types that we want the compiler to infer. Let’s take a look at how those placeholders work, and what kinds of situations that they could be really useful in. * Podcast episode 114: “ACCESSIBILITY ON APPLE’S PLATFORMS” WITH SPECIAL GUEST SOMMER PANAGE * accessibility * ui development Published on 21 Mar 2022 Sommer Panage returns to the show to discuss Apple’s various accessibility APIs and tools, how to incorporate accessibility support into a team’s overall development workflow, and what it was like being an engineering manager at Apple. * More to read BROWSE ALL ARTICLES * More to listen to BROWSE ALL PODCAST EPISODES * More to watch BROWSE ALL VIDEOS Copyright © Sundell sp. z o.o. 2023. Built in Swift using Publish. Twitter | RSS | Contact