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…