Angelo Stavrow [dot] Blog

Swift

I just filed “suggestion” feedback FB9240072 with Apple on the MFMailComposeViewController in UIKit.

Read more...

I'm thrilled to announce the release of [WriteFreely for iOS] on the App Store. 🚀

Read more...

UPDATED: I opened my original PR against the wrong branch, so I've fixed the links here to point to the new PR.

I've been wanting to learn more about building macOS apps. SwiftUI multiplatform apps are a great way forward in building something quickly, but you'll often still need to drop into AppKit and UIKit to solve things that SwiftUI just doesn't address (yet). So, for someone that's got some experience building iOS apps, what's a good way to learn?

I could just build a bunch of toy projects on my own. I read through Hacking with macOS which taught me a tonne. But for myself, the best learning is social learning — by observing and interacting with others.

I've been lurking on the Slack group for NetNewsWire for months, and once I felt comfortable with the way the community worked together, I asked to tackle a first issue. Nothing especially complex for my level of experience, but that would have me digging through the codebase to figure out how things work, and learn how to make the changes for a framework (AppKit) that I'm less familiar with.

I like digging through a codebase and teasing apart the way it works. The advantage of poking at NetNewsWire is that it's a nicely-organized project, clearly structured for long-term maintainability by anyone willing to contribute.

(If we're ever able to sit and enjoy an afternoon restorative together, ask me about having to port a 20 kLOC, uncommented, single-file code project to another language.)

(On second thought — don't.)

It's one thing to learn how the Notifications API works by changing the way a feature works. But there's a lot to learn by looking at how others structure and organize their codebase, set up their build steps, share code between iOS and macOS targets, &cet. You just don't get that depth from simple how-to tutorials. Those tutorials have their place in the learning process, and they're a wonderful community resource! But you have to go deeper to go from “my first massive view controller app” to building a complex software project.

Another nice thing about contributing to NetNewsWire is that the community is wonderful. One thing I appreciated was that I was trusted to tackle the problem myself, rather than be told (more or less) what to do as a first-time contributor. Of course, any questions I had were answered, and whenever I asked for feedback it was honest and helpful. That's a testament to the community Brent Simmons has built around the project.

And so, I opened my first pull request this morning. In service of showing messy work (and because it's good Git hygiene, IMO), the work's broken up into a dozen fairly small commits. You can see some of the comments I removed after figuring out what exactly happens when you fetch the user's notification settings, for example.

I'm looking forward to the team's feedback on my proposed changes, and I'm looking forward to contributing more to the project!

#projects #swift #netnewswire

Using @EnvironmentObject is a great way to share data between your views. Previously, I'd been using @ObservedObjects all over my views, and it felt clumsy.

Hot tip: by setting an environmentObject for a NavigationView, any children of this NavigationView can then add a property like @EnvironmentObject var someType: SomeType. SwiftUI then gives them access to the observed object, without you having to pass the object down the navigation tree and through views that don't need access to it:

/* ContentView.swift */

import SwiftUI

struct ContentView: View {
    @ObservedObject var someType: SomeType

    var body: some View {
        NavigationView {
            MyView()
        }
        .environmentObject(someType)
    }
}

/* MyView.swift */

import SwiftUI

struct MyView: View {
    var body: some View {
        SomeTypeList()
    }
}


/* SomeTypeList.swift */

import SwiftUI

struct SomeTypeList: View {
    @EnvironmentObject var someType: SomeType
    
    var body: some View {
        // Do something with someType
    }
}

But! If you were passing it in to a child view as an ObservedObject and had set up some test object for use in your SwiftUI preview? If you try to pass in your test object, you'll get an error:

Cannot convert value of type 'SomeType' to expected argument type 'EnvironmentObject<SomeType>'

To fix this, use the environmentObject modifier on your child view's preview provider:

struct SomeTypeList_Previews: PreviewProvider {
    static var previews: some View {
        SomeTypeList()
            .environmentObject(testSomeType)
    }
}

Yay, your preview works again!

#swift #swiftui

Hot on the heels of the WriteFreely Swift Package, I'm kicking off another fun open-source project: building a WriteFreely client for all Apple platforms as a SwiftUI multiplatform app.

I've written about the project a bit more here, where I'm sharing updates on the WriteFreely/Write.as work I do. If WWDC got you interested in learning more about multiplatform apps and SwiftUI, join me in building this! The goal is to go from what you see in the GitHub project today to a functional app by the end of August. Developers of any level of experience are welcome!

This work will have two tracks — building the client app, which will be the bulk of the work, and improving the Swift package. Personally, I'm more excited about SwiftUI and multiplatform apps than I've been about other tech stacks in a long time.

That said, one of the reasons Per has been stagnating for as long as it did was because I got bitten by the Swift 2 → 3 transition, which kinda broke everything. I'm hoping that it'll be smoother for SwiftUI as it's improved and extended over the next few years.

More TK!

#writeas #projects #swift #swiftui

A little bit of a UI/UX deficiency I've found when using Swift Package Manager in Xcode 12 β2 is that adding a Swift package to a multiplatform (iOS/macOS) SwiftUI app requires you to choose a target for the package:

That means that if you have a platform-agnostic package that you import, and try to use it for both the iOS and macOS targets, you'll invariably run across an error (”No such module 'ModuleName'”) when you're writing code against the target you didn't add it to in the above step, and your project won't build for that target.

To fix this, Stuart Breckenridge shared the following tip: go to the Build Phases tab for each of your targets, and make sure it's added under Link Binary with Libraries:

Build the app, and you'll be good to go.

I've filed FB8094575 to improve this user flow.

#spm #swiftui #swift

It's been a while since I posted an update here, and I wanted to announce a new project: a WriteFreely Swift package that you can drop into your Mac and iOS apps.

You can find it here: https://github.com/writeas/writefreely-swift

What's WriteFreely?

WriteFreely is an open-source platform for writing on the web. It powers the Write.as service (find me here!), and lets you build writing communities on the web.

I was introduced to Write.as and WriteFreely while working with Glitch, where I got to chat about the service and its principles with Matt and CJ. Matt wrote a thoughtful post on Glimmer about the importance of privacy for creating on the web, and CJ built a tonne of cool sample apps that connected to the Write.as service for their Glitch team.

Cool, Tell Me More About The Project

This project represents a couple of firsts for me:

  • This is the first time I've worked on a Swift package
  • This is the first time I've wrapped a RESTful API in Swift
  • This is the first time I've worked with URLSession and Result in Swift

Right now, it's an alpha/developer-preview release. There's a lot of room for improvement here, and I'm looking forward to working towards a 1.0 with the WF community.

As I mention in this forum topic and my Write.as post, the design for the WriteFreelyClient is to leverage completion blocks that return a Result tuple with either a User, Post, or Collection (or an array of these types where that makes sense), or an Error on failure. That makes it pretty easy to build completion handlers:

func loginHandler(result: (Result<User, Error>)) {
    do {
        let user = try result.get()
        print("Hello, \(user.username)!")
    } catch {
        print(error)
    }
}

guard let url = URL(string: "https://your.writefreely.host/") else { fatalError() }
let client = WriteFreelyClient(for: url)
client.login(username: "username", password: "password", completion: loginHandler)

What's Next For The Project?

Good question! There are definitely some major to-do items that are obvious to me:

  • Add a test suite (there will be some refactoring required to facilitate this)
  • Create generic-ish request templates to DRY out the WriteFreelyClient public methods
  • Extend for use with the Write.as platform

Mostly, though, I'm excited for people to try it out and let me know how it works for them!

#writeas #projects #swift