[SC]()

iOS. Apple. Indies. Plus Things.

Converting Data into a Formatted JSON Swift String

// Written by Jordan Morgan // Feb 2nd, 2023 // Read it in about 2 minutes // RE: Swift

Recently I’ve started a large project which involves converting a number of models written in Objective-C over to Swift. As part of my work, I’ve been porting swathes of model definitions that are represented by a flavor of JSON that were used with Pinterest’s excellent Plank library1.

I quickly discovered that there wasn’t a fantastic shortcut to this work, since the definitions we’ve created for our Objective-C codebase aren’t plug-and-play JSON. Here’s an example:

{
    "id": "composerLinkImage.json",
    "title": "composerLinkImage",
    "description" : "Schema definition of Multiple Composer Link Image",
    "$schema": "http://json-schema.org/schema#",
    "type": "object",
    "properties": {
    	"height" : {"type" : "integer"},
        "width" : {"type" : "integer"},
		"type" : {"type" : "string"},
		"url" : { "type": "string", "format": "uri"}
	}
}

If we want to use some model generation tool to get us 90% of the way there, such as QuickType, this won’t cut it for a few reasons:

  1. There’s extraneous data I don’t need.
  2. The only things I do need are keyed to properties.
  3. Plus, while it lists the properties and their types, that’s not super awesome because what I actually need are examples of those properties with their values (i.e. "width" : 200).

At best, I could copy and paste the properties and tweak some of it to work:

{
    "height" : 0,
    "width" : 0,
	"type" : "",
	"url" : "https://www.test.com"
}

At this point, QuickType will spit out most of what I’m after:

// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)

import Foundation

// MARK: - Welcome
struct ComposerLinkImage: Codable {
    let height, width: Int
    let type: String
    let url: String
}

Of course, this doesn’t scale when I have near 50-60 models to convert over. So, taking stock of the situation, I accepted the fact those pesudo-json definitions weren’t worth much. It was like having a cheat sheet for a test, but all of the answers were written backwards.

But!

What I did have was an internal app I created to hit all of our endpoints to fetch those models:

Screenshots of an internal iOS running that shows APIs and their responses.

And therein lied my salvation.

By hitting our own API, I could get grab the raw Data and then use a little extension to get that into a formatted JSON string. With that, I could get cut out of some of the grunt work by copy and pasting the correctly formatted JSON into a model generation tool. There’d still be tweaks to make, but it does save me a bit of time.

And so it was, I used #ThisOneSimpleTrick it to create a beautiful, copyable and readable JSON string using our good friend Foundation:

extension Data {
    var prettyPrintedJSONString: NSString? {
        guard let jsonObject = try? JSONSerialization.jsonObject(with: self, options: []),
              let data = try? JSONSerialization.data(withJSONObject: jsonObject, 
              										 options: [.prettyPrinted]),
              let prettyJSON = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { 
              	return nil 
         	  }

        return prettyJSON
    }
}

From there, I simply looped through our API calls and used this little helper function to print everything out that I needed:

func dumpRawJSONResponse(for endpoint: URL, params: [String : Any], method: HTTPMethod) {
	Task {
	    let response = await AF.request(endpoint,
	                                    method: method,
	                                    parameters: params)
	                                    .serializingResponse(using: .data).response
	    
	    guard let responseData = response.data else {
	        print("There was no data from the response.")
	        return
	    }
	    
	    print(responseData.prettyPrintedJSONString ?? "Couldn't create a .json string.")
	}
}

And zing ✨ - there was all of the JSON I needed perfectly formatted. From our previous example, now I had something like this:

{
	"height" : 200,
	"width" : 200,
	"type" : "square",
	"url" : "https://www.test.com"
}

And that, as my good friends in the U.K. would say, is a job that’s done and dusted.

Until next time ✌️

  1. This tool has saved me several hours, and it’s perfect for generating immutable models for Objective-C codebases. 

···

A Less Evil NotificationCenter?

// Written by Jordan Morgan // Jan 24th, 2023 // Read it in about 3 minutes // RE: Tech Notes

Look, I get it. I shouldn’t be spamming NotificationCenter to do things like show a banner in SwiftUI:

But I also shouldn’t eat these little nuggets from heaven at 11:00 p.m. at night, and yet…

And so it is, I found myself in a little bit of a pickle. Here’s the scenario. In my upcoming basketball app shown above, I want to let folks know about a critical gesture to erase their strokes. After they draw their first stroke, I show the aforementioned banner.

Therein lies my issue, because SwiftUI begets so much sweet, beautiful view abstraction - I’m essentially several layers deep in my view hierarchy. That means the place which shows the banner has no clue about the place where the PencilKit strokes are drawn:

An exploded view of the basketball app's view hierarchy showing how the view that needs to trigger the banner is far in the view tree from the view which will show it.

Or, expressed as code - it’s a bit like this:

struct ViewOne: View {
	@State private var showTip = false

	var body: some View {
		VStack {
			StuffAndThings()
			ViewTwo()
		}
		.customBanner($showTip) // The Banner!
	}
}

struct ViewTwo: View {

	var body: some View {
		VStack {
			MoreStuffAndThings()
			ViewThree()
		}
	}
}

struct ViewThree: View {

	var body: some View {
		VStack {
			EvenMoreStuffAndThings()
			ViewFour()
		}
	}
}

struct ViewFour: View {
	var body: some View {
		ZStack {
			Stuff()
			WrappedPencilKitView() // Logic to fire banner here
		}
	}
}


To take care of it to hit my internal ship date, I took a shortcut. Enter the air-horn of NotificationCenter.

// Abbreviated for clarity...

final class AppSettings: ObservableObject {
    @AppStorage("hasSeenPencilKitTipsView") var viewedPKTips: Bool = false
}

// The wrapped PencilKit stuff
extension DrawingViewController: PKCanvasViewDelegate {
    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
    	let settings = AppSettings()

    	guard !settings.viewedPKTips else {
    		return
    	}

        NotificationCenter.default.post(name: .ehFirstPencilStroke, 							    object: nil)

        settings.viewedPKTips = true
    }
}

// The SwiftUI View
struct WhiteboardView: View {
	@State private var showPencilTips = false

	var body: some View {
		/* Bunch of SwiftUI */
		.showCustomBanner($showPencilTips)
		.onReceive(NotificationCenter.default.publisher(for: .ehFirstPencilStroke), perform: { _ in
    		showPencilTips = true
		})
	}
} 

And that does #MostlyWork™️. But the fangs of blasting notifications can show themselves quite quickly. For one, there are two places that use the same drawing view, but I only want one of them to show this particular banner. Secondly, there are other custom notifications I use too, which means my View can end up looking like onReceive purgatory:

// The SwiftUI View
struct WhiteboardView: View {
	@State private var showPencilTips = false
	@State private var showSubsTips = false

	var body: some View {
		/* Bunch of SwiftUI */
		.showCustomBanner($showPencilTips)
		.onReceive(NotificationCenter.default.publisher(for: .ehFirstPencilStroke), perform: { _ in
    		showPencilTips = true
		})
		.onReceive(NotificationCenter.default.publisher(for: .ehShowSubstitutionExplainer), perform: { _ in
    		showSubsTips = true
		})
		/* And on...*/
	}
} 

So, for the time being, I just tried to make things a little nicer. One notification to rule them all. This helps me package up identifiers to avoid showing things at the wrong places while also ensuring I only require on .onReceive modifier for any custom domain logic using NotificationCenter:

extension NSNotification.Name {
    static let ehNotification: NSNotification.Name = .init(rawValue: "eh_notification")
}

extension NotificationCenter {
    func ehPublisher() -> NotificationCenter.Publisher {
        return self.publisher(for: .ehNotification)
    }
}

struct EHPayload {
    enum Scenario: Hashable {
        case didDrawPencilStroke,
             requestTeamFetch(courtID: Int64),
             showBench(courtID: Int64)
    }
    
    let scenario: Scenario
}

Basically, what we’ve got now is one abstract notification that fires, an Enum that identifies what kind of data or scenario the notification represents and one tiny little extension to get at a publisher easier.

So, the above now looks like this:

// Abbreviated for clarity...

extension DrawingViewController: PKCanvasViewDelegate {
    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
    	let settings = AppSettings()

    	guard !settings.viewedPKTips else {
    		return
    	}

        let noteScenario: EHPayload = .init(scenario: .didDrawPencilStroke)
		NotificationCenter.default.post(name: .ehNotification,
			                            object: noteScenario)

        settings.viewedPKTips = true
    }
}

// The SwiftUI View
struct WhiteboardView: View {
	@State private var showPencilTips = false
	private let nc = NotificationCenter.default

	var body: some View {
		/* Bunch of SwiftUI */
		.showCustomBanner($showPencilTips)
		.onReceive(nc.ehPublisher()), perform: { note in
			guard let payload = note.object as? EHPayload else {
				return 
			}
            guard payload.scenario == .didDrawPencilStroke else {
            	return 
            }

    		showPencilTips = true
		})
	}
} 

And voilà. There is type safety, flexibility and only one onReceive modifier.

Final Thoughts

Look, sometimes in this business, you just have to sing for your supper. Is this whole thing in service of me thinking in UIKit terms in a SwiftUI context? Probably. This is one of those fun situations where you’ve got to dress up some ugly code and move on. If the time comes where this isn’t stable or I need to refactor things, I’ve been told passing an environment key would probably be the better option.

But for now, my banners work!

Until next time ✌️

···

Spend Stack is Now Open Source

// Written by Jordan Morgan // Jan 15th, 2023 // Read it in about 1 minutes // RE: The Indie Dev Diaries

Good news - after talking with Spend Stack’s owners, they have agreed to let me open source it!

The version I can share is the last one I worked on personally (version 1.3.4). First, let me get the legalities and some questions out of the way:

  1. I have to assume all liabilities with it as part of the agreement. So, you know, don’t do something terrible.
  2. You can ship an app based on this code, you cannot name it Spend Stack. This means you would need to change all references to the name “Spend Stack” within the source code as part of your adaption.
  3. However, if you do ship an app based on this you must include the following somewhere in the app and link to this post:

“This app is based upon Spend Stack’s open source code.”

And finally, while not a requirement, I’d love it if you told me about any app you ship based off of this code so that I can link it here to share the word.

👉 Here is the repository on Github. 👈

And that’s about it.

Second Wind(s)

As I noted recently, I was sad to see Spend Stack sunset from the App Store. Still, two years after it was acquired, I received a few number of messages asking me where it went. I was saddened to think that nobody could use it anymore, especially since I still rely on it to this day to manage my monthly subscriptions.

But now - one of you can take it on! Or several of you. I am delighted to know that it can come back in some form from someone in the community.

Please keep in mind, it’s been over two years since this code was last touched. There are some build warnings and cobwebs, but nothing you can’t dust off yourself. With that said, here are two interesting things to check out:

  1. CloudKit: Spend Stack is built on a “custom” CloudKit engine. I say custom in that, I setup all of the zones and records myself. It doesn’t use any CoreData automatic sync solutions. Check out SSCloudKitManager.h to see how it all fits together. Specifically, if you want to follow along step-by-step, go to -(void)checkAccountStatus to where it all starts, creates subscriptions and zones - all the good stuff.

  2. Custom Transitions: The custom view controller transitions were some of my favorite parts of Spend Stack. Look under the Controllers/Modals group to see all of the files for most of them. For example, this one when you first create a list:

Spend Stack's custom transition to create a list.

Or, the settings modal card transition:

Spend Stack's custom transition to view settings.

And, if you need to see how interactive transitions fit together, then maybe the one to view images or the HD icons would interest you:

Spend Stack's custom transition to view images interactively.

There are a bunch of other fun things, and so long as you know your way around Objective-C, there is a lot to tinker with.

Final Thoughts

I am grateful that I was granted permission to share Spend Stack as I last left it. Maybe now, it’s not dead after all. I hope one of you take it and potentially do what I and its new owners could not - find true product market fit, find a worthy subscription model and see it thrive.

Or, maybe you just want to poke around and see how CloudKit all fits together. That’s fine too. Or look at the custom transitions. Maybe you just want to build it locally and use it as it last was (which is what I do).

No matter, Spend Stack can now see a second life, and that makes me very happy.

Until next time ✌️

···

Just a Little Nicer

// Written by Jordan Morgan // Jan 10th, 2023 // Read it in about 2 minutes // RE: The Indie Dev Diaries

It’s always the small things, isn’t it? That’s what makes software great to me. Personal little touches. Lately, I’ve been trying to codify these little moments which make apps just a little nicer. What are they, really? I wrote quite a bit about this very concept in my book series, but I wanted to dig in further.

I think I’ve landed on this definition: They aren’t necessary, but they are welcome.

Exhibit A

Recently, while working on Win the Week, I needed a vanilla permissions view to grant access to health data. While it’s only me using this app, I still wanted to make it look as if someone else could be using it someday:

A screenshot of a goal tracking app with blocks of different habits to track and goals.

I think that’s good, and I like it. A little cheeky copy, some contrast-y colors, rounded fonts to promote the vibe - it’s working for me. And, to make it just a little nicer, I decided to throw some pop into it using Pow:

A screenshot of the same image above, only now the heart icon animates and pops.

Certainly not necessary, but perhaps when you first see it - it’s a welcome, fun little interaction. Situations like this are ripe for making them a little bit nicer because people won’t come across them much. If people would see this permissions screen, say, every time they opened the app - then it would be incredibly annoying.

So, the trick is also being able to discern when to make things just a little it nicer.

Some Other Examples

Let’s visit one of the all time greats, the Apple Design Award winner Things 3. When you resize the window on macOS and hit the mimnimum size, you get a nice little bounce:

Things 3 by Cultured Code being resized to its minimum size and doing an animation to reinforce that.

Of course, Things 3 could just as well go to the minimum size and stay put when I released the mouse pointer after I was done resizing. No problem there. But, this is just a little bit nicer. And people like nice things.

Going back to my own work, I loved the little springy animations in Spend Stack’s onboarding:

Spend Stack's onboarding animation.

Or, if you simply want a cheat code to seeing several nice little touches, just download Up Ahead. It’s full of them. Notice the little bobble at the top when touching the binocular guy at the top in the navigation bar:

Up Ahead's navigation bar animation.

Accessibility

You may have noticed that several of these little tiny touches involved animations, transparency, blurring and scaling. In many cases, folks would want to forgo these things due to accessibility reasons, so take care to query these types of settings during implementation:

// SwiftUI
struct SplashView: View {
  @Environment(\.accessibilityReduceMotion) var axMotion

  var body: some View {
    VStack {
      // UI
    }
    .onAppear {
    	if !axMotion {
    		// Do animation
    	}
    }
  }
}

// UIKit
if !UIAccessibility.isReduceMotionEnabled {
	// Do animation
}

Both SwiftUI and UIKit have extensive support to query these types of values. Don’t miss them!

Update What else is new? Ian Keen shared with me a slick modifier to automatically disable animations according to accessibility settings:

extension View {
    public func accessibleAnimations() -> some View {
        modifier(AccessibleAnimationModifier())
    }
}

private struct AccessibleAnimationModifier: ViewModifier {
    @Environment(\.accessibilityReduceMotion) private var reducedMotion

    func body(content: Content) -> some View {
        content.transaction { if reducedMotion { $0.animation = nil } }
    }
}

// In use...
struct SplashView: View {
  var body: some View {
    VStack {
      // UI
    }
    .accessibleAnimations()
}

Final Thoughts

Making things just a little nicer is one of my favorite phases of app development. I rarely think about it during design, but rather - I find it along the way, if that makes sense? I love coming across these things in other apps, it really adds a element of fit and finish. It’s rare to find these things done well, and they are moreso a shibboleth that only skilled designers or developers know that unlocks membership to nice, quality iOS apps.

Of course, all the usual rules apply. Don’t overdo it, respect accessibility - you know the drill. We are usually great about following those rules, but at the same time, don’t sleep on making your apps fun and just a little nicer.

Until next time ✌️

···

Win the Week: My Homegrown App for 2023 Goals and Habit Tracking

// Written by Jordan Morgan // Jan 3rd, 2023 // Read it in about 3 minutes // RE: The Indie Dev Diaries

Ah yes, a new year. A new you. Or the same you, with different goals and aspirations? Who knows. Regardless, I’ve always been one to write down what my path ahead should look like if it all went well during my next trip around the sun. And for many, many years I’ve done it several different ways.

This year - I decided to try something a little bit different to track the goals I want to reach and the habits I want to form and maintain. The idea is simple: Instead of manually ticking “I did this” or entering in stuff to confirm I’m doing X or Y, I instead wanted a simple dashboard that:

  1. First and foremost, reminds me what my goals are for the year.
  2. And, automatically pulls what it can from iOS to show how I’m doing.

TL;DR: Much less manual input or data entry, much more glanceable information on what 2023 should look like. The result is what I call “Win the Week”, and this is what it looks like so far:

A screenshot of a goal tracking app with blocks of different habits to track and goals.

The Kickoff

Of course - there was a lot of thought wrangling that led up to this point. For 2023, I knew I wanted to simplify things a lot. For example, like probably everyone else reading this, I want to get in better shape.

In years past, this meant I would pop some goals in the excellent Streaks app. This time, though, I wanted a less prescriptive approach - so I made my health goal much more concrete, it’s simply to get my abs back. With a goal like this, my hope is that the steps to get there could be more abstract and malleable.

This theme continued on, and I was able to gather all of my thoughts using Apple’s new Freeform app:

A screenshot of Freeform by Apple with yearly goals and resolutions in place.

After getting everything out of my head, I noticed that my goals ended up being split into daily habits, monthly goals and year-long goals:

A screenshot of Freeform by Apple with yearly goals and resolutions in place.

This is also where the name came from, as the more and more I looked at where I wanted to go it became clear that if I can slowly “win the week” most of the time, I’ll likely achieve what I’m after. Think of it as a slight deviation from habit-savant James Clear’s popular “1% better each day” mantra.

For me, after weighing the pros and cons of several projects, ideas and high aspirations, this is what I ended up with. In 2023, I want to:

  • Daily: Pray, eat vegetables and workout (however big or small).
  • Monthly: Write two chapters for my book series, weigh in along with a progress picture and write on my blog (Hey! Already nailing that one!)
  • On the year: Ship two imperfect, M.V.P. apps, beta this one, get my abs back and read two books.

In many ways, this year is about kicking off initiatives that I want to be able to grow in 2024. Once I had these defined, I hit a wall when considering how I want to collate all of this information, otherwise track it and keep tabs on it. What I wanted was something that I can quickly start my day with, and check up on as the day goes along (and the week, month and year) to get a feel for how things are going and remind myself of what I want to accomplish.

So, of course, as an iOS developer…..all roads lead to opening Xcode and making an app.

The M.V.P. of M.V.P.

At this point, I already had mapped out roughly where I wanted to go. The goals were set and the direction was clear, it was more about finding out the absolute best way specifically for me to digest all of this. I ended up gravitating towards a dashboard direction that only has one view, and that view is made up of little “widgets” that you might find on your homescreen. I wanted a tiny little card for my overall goals, maybe one for a progress picture for each month, or one to simply list my daily goals, etc.

Once again, I used Freeform to map all out this out:

A screenshot of Freeform by Apple with yearly goals and resolutions in place.

let concatenatedThoughts = """

This also follows the concepts I laid out in the design book of my Best-in-Class series. Do you make a high fidelity Sketch file, scratch it out on a napkin or something in between? The answer is, of course, whatever works best for you and your goals.

"""

I was able to get done about half of what I had set out to do, but I’m very happy with where I was able to get to (no small thanks to SwiftUI):

A screenshot of an internal habit tracking app.

Each little card is what I call a “block”, and here are the ones I was able to finish:

  • The contextual block, a.k.a. Mr. Rotaty Boi: This one is at the top. It changes what it shows throughout the day and toggles during my definitions of early morning, morning, afternoon, kinda night and night. Of those, it has health tips, HealthKit metrics that are important to me that show how I fared from the day before, caffeine reminders and warnings, little nice-isms and sleep charts powered by the new Charts framework.
  • The Daily block: This displays all of my daily habits I want to build, and represents the only form of user input in the app. You can simply tap the circle associated with the day and habit to see if you’ve done them, and it simply resets each week.
  • The Monthly block: This one, obviously, shows my monthly goals as a list. I struggled with this one design wise the most, as it’s a bit squished right now to make room for the grid-like interface that the daily block required.
  • The Yearly block: A fun, colorful little text-based block that shows all of my yearly goals.
  • The Progress block: This shows the progress of the year using a simple bar indicator. Anxiety-inducing for some, but motivating for me to gain perspective on how fast time flies.
  • The Healthy Reminders block: This displays a bunch of quotes and sayings I’ve collected over the years that were stored away in Shortcuts. It rotates throughout the day.
  • The Bible Verse block: Exactly the same as above, only for bible verses.

And, here are the ones I didn’t get to:

  • One for progress pictures along with weight measurements for each month.
  • A block to show calories consumed minus calories burned to give a better picture of my remaining calorie budget.
  • Something I call the “Link Book” which simply deep links to other things relating to my goals such as an inspirational blog post, a journal entry in Day One, etc.
  • A timeline of the year that better shows when my year-long projects should be done. This is the one I will do next, as it’s quite important to me.
  • A block for showing what a successful end of the year looks like for me.
  • And finally, while not blocks, I wanted to tap into any block and associate a tiny journal entry or pictures along with it.

So that’s Win the Week. I’m sure I’ll hack on it as the year goes on and I’ll update everyone on its progress. Until then, may your year be filled with…exactly whatever you want ❤️!

Until next time ✌️

···