[SC]()

iOS. Apple. Indies. Plus Things.

Morphology in Swift

// Written by Jordan Morgan // Mar 28th, 2023 // Read it in about 5 minutes // RE: Swift

Morphology is more exciting than what the definition on the tin might suggest. The study of the formation of words certainly doesn’t garner hot conference talk C.F.P.s, nor does any A.P.I. around the subject beget much praise.

But maybe it should?

After all, we’ve all seen the “morphology version” of a cardinal sin, where strings end up reading like this (from left to right):

  • “She will receive 0 game”: Awful +.
  • “She will receive 0 game(s)”: Awful -. Passable but lazy.
  • “She will receive 0 games”: Correct!

Examples of incorrect pluralization in copy.

And the problem becomes even more exacerbated once the underlying data model is mutated, and the user interface doesn’t keep up.

Why does this kinda thing happen? Well, in short, it’s a little bit of a pain in the [REDACTED] to get it right. What seems simple in terms of engineering can turn into boilerplate, drone-worthy code that’s trivial to get wrong. Take our video game shopping sample app from above, maybe it had code that looked similar to this:

if selectedGames.isEmpty {
	checkoutString = "She will receive 0 games."
} else if selectedGames.count == 1 {
	checkoutString = "She will receive 1 game."
} else {
	checkoutString = "She will receive \(selectedGames.count) games."
}

And that’s not even considering localized strings. Regardless, it’s a tawdry implementation that too many apps end up shipping anyways. Even still, it’s a problem that has more nuance than that.

Why Morphology is Inherently Difficult

Think of all of the beautiful intricacies our languages have. For example, in English, adding the prefix “re-“ to a verb indicates repetition, as in “do” to “redo”. Or, take Spanish, where the suffix “-azo/-aza” added to a noun refers to a sudden, forceful action, as in “puerta” to “portazo”.

Those are fairly elementary examples, so perhaps you’re looking for a more…colorful illustration? If so, may I suggest to you…

antidisestablishmentarianism?

This word, one of the most pure examples of morphology in the English language, refers to a political stance which opposes the removal of a state-sponsored religion or church. During the 19th century, it was specifically used during debates concerning the disestablishment of the Church of England.

The word itself, though? It consists of several smaller morphemes which are combined to form a single word that has a very specific meaning:

  • “Anti-“ is a prefix that means “against”
  • “dis-“, another prefix, means “not” or “reverse”
  • “establishment”, the root word, refers to a governing or controlling group
  • “-arian”, a suffix, which means “pertaining to” or “advocating for”
  • And finally, “-ism” is a suffix that means “belief in” or the “practice of.”

Thus, we arrive at antidisestablishmentarian.

All of these formations, the things they mean and all of the ways that words can change by virtue of them, are examples of morphology. And in programming, we encounter interesting cases of morphology all the time, whether we realize it or not.

In short, many languages use certain terminology to express all sorts of things about someone or something, and because of that - there are several ways in which they combine nouns, gender and their pluralizations to produce sentences uniquely tailored to a certain audience.

When those words are structured correctly, they are achieving a form of grammar agreement. When they don’t, well - see the picture above once more.

Back to Swift

So, now that we have a passing idea of what morphology is, and what it looks like when it’s represented incorrectly - how do make sure we’re getting it right in our own apps? Are we destined to write code that includes several localization entries, and if statements determining pluralization cases?

Of course not. Fortunately, this problem has been solved by Foundation since iOS 15. In fact, the above issue can be solved like this:

Text("She will receive ^[\(count) games](inflect: true).")

And now, we correctly get all of the string values we’d expect:

Examples of correct pluralization in copy.

This means you can delete a lot of “Use this string for this quantity/condition/etc” code, along with their localized string entries.

Using Inflection

You’ve likely noticed a few interesting things about the string above. Notably, it uses a special kind of interpolation that follows this format:

^[/*the string*/](inflect: true)

This opts the string into automatic grammar agreement, and so long as we’re using an AttributedString type and localized strings1 within them, Foundation now takes care of the rest.

Further, Foundation can also efficiently handle terms of address. In our example, I was buying video games for my wife, i.e. “She will receive…”. But, what if I was buying them for a brother, or I had no idea who I was buying it for?

In recent versions of iOS, there are options to select your preferred term of address found under the Language & Region settings. In languages, such as Spanish, this option is used with the grammar agreement engine to create more personalized, grammatically correct strings:

Setting your term of address in iOS' settings in Spanish.

Here, the system can more accurately represent the user throughout iOS in Spanish speaking locales. For example, the word “bienvenido” changes depending on the term of address someone is using. Each option would result in a different, more grammatically correct string for the word “bienvenido” and others like it. This better aligns with how Spanish speakers naturally communicate, as they may use diverse forms of a word based on the context of a conversation.

What does this mean for you and I? It means that Foundation allows us to not worry about any of this, and it will correctly use “bienvenida” for a female address, and “bienvenido” for a masculine tone.

Further, let’s say the user hasn’t selected any option for their term of address. Or, perhaps they have not granted apps access to it (in the screenshot above, that’s what the bottom toggle is for). In those cases, you can provide a generalized, all-purpose string to be used by including the inflectionAlternative parameter.

In fact, this is similar to how Apple handles its “Welcome to Notes” copy:

Text("^[Used if there is a term of address](inflect: true, inflectionAlternative: 'Used if there isn\'t').")

Avoiding String Interpolation

You may be amazed that such a heavy lifting A.P.I. even exists in Foundation, but perhaps you’re a little scared off by the…stringyness of it. It’s true that it’s perhaps too easy to screw all of this up. For example, can you quickly spot why this doesn’t work?

Text("She will receive ^[\(count) games](inflected: true).")

No? I accidentally wrote inflected instead of inflect.

If you’ve been around the iOS block for a bit, perhaps this is all a little bit reminiscent of the visual format language of yore. Powerful and handy, no doubt - but it could also be a foolhardy endeavor. No matter, you can strongly type the process too by way of the Morphology struct.

This way, you can perform manual grammar agreement at runtime. There are a couple of reasons as to why you’d go this route. For example - maybe you don’t have the data about someone until runtime to make a decision about which morphology rules should be used.

Every AttributedString has a InflectionRuleAttribute key for you to assign a Morphology struct to. In fact, that’s what the grammar engine is doing under the hood with our string interpolation code in our examples up to this point. All the same, we could rewrite our video game example at the top of the post from this:

Text("She will receive ^[\(count) \(games)](inflect: true).")

…to this:

private func strongTyped() -> AttributedString {
    var string = AttributedString(localized: "They will receive \(count)
games.")
    
    var morphology = Morphology()
    
    // In my case, I'm sending these to my wife. So we will set a feminine gender.
    morphology.grammaticalGender = .feminine
    
    let number: Morphology.GrammaticalNumber
    
    switch count {
    case 0:
        number = .zero
    case 1:
        number = .singular
    default:
        number = .plural
    }
    
    morphology.number = number
    string.inflect = InflectionRule(morphology: morphology)
    
    let formattedResult = string.inflected()
    return formattedResult
}

…and we get the same result.

You can do a lot of fine tuning with Morphology, from using different pronouns on a per-language basis to refer to a third-person context, to checking whether or not the device’s selected language supports grammar agreement2 by passing in its BCP 47 language code:

if InflectionRule.canInflect(language: "en-IE") {
	// Supports Ireland
}

Final Thoughts

Foundation is still, many years on, the unsung hero of iOS. It underpins so much of iOS programming, and without it - we’d spend every single day reinventing the wheel.

And with that, I hereby decree that henceforth, you are no longer but a simple programmer, but also officially a morphologist. You are no longer going to correct strings in your app, you are performing complex morphologic enhancements.

Until next time ✌️

  1. I should note that it technically can be used without localized string entries. If the scheme’s language is defaulting to one that is supported by Foundation’s automatic grammar agreement feature, it’ll work out of the box. 

  2. As far as I can tell from the documentation, Spanish and English are the two languages supported as of iOS 16. 

···

10 Years of Hacks and Fixes: A Retrospective of My First Decade of Coding

// Written by Jordan Morgan // Mar 21st, 2023 // Read it in about 7 minutes // RE: The Indie Dev Diaries

In what seems like the fastest decade of my life, I realized that this year, I’ve been in the programming workforce for a decade. Though, my coding journey starts earlier than that — at around 2008 I believe, but as far as being paid to do this stuff?

Yep, 10 years now.

Instead of taking you down memory lane with a 4,000-page Iliad of my observations, I thought I’d just spill out the first thoughts that stuck out when thinking about the last decade of programming “professionally” (i.e. – at a job). These thoughts below go more or less in chronological order, starting around 2013 to today.

I Was Mesmerized by Technology

Like anything in life, it appears that the things really worth doing are the things you are truly interested in. Things that really excite you. For as long as I can remember, technology has always been that for me. From the moment my dad plopped down a Super Nintendo on our kitchen table in 1992, I was enamored.

As I played Mega Man X over and over again, a seed was planted. While I thoroughly enjoyed the game, I started to wonder—how is it actually working? How was it made? Can anybody do something like this?

In short, a burst of creativity was planted in my psyche at a very young age, and I believe that was a foundational event for me. It never left. As I grew older and, of course, the iPhone was released my senior year in High School – I started to wonder if I could somehow be involved in the tech world.

At first, I came across Gamesalad - which is still around today! I credit this little W.Y.S.I.W.Y.G. editor for truly making my passion take off. It was the first time I thought of something, and then made it! Now that I had a taste of making something for iOS, I simply wanted more.

And so—I did what most people did in 2007, I went to a book store and bought a book over iOS programming. And I didn’t get a lick of it, like…at all. I couldn’t comprehend how anybody could really do this coding stuff. Why were the terms function and method used interchangeably? Are they different? The same? Extrapolate that thought out to decidedly more difficult concepts, such as collections or the difference between an array and a set. I wasn’t getting anywhere. This is the part where things got hard.

Which leads me to my next thought.

I Needed School

For my learning style, books and video courses just weren’t doing it. And for one simple reason – I needed to talk to someone at length about the questions that I had. I couldn’t learn the basics by myself.

Enter Ozarks Technical Community College. I enrolled in their C.I.S. program and within a few years (all of my general education was already finished) I had my associates degree all buttoned up. But not only did I get it, I was good at it, too! I was near the top of my class, even though it was a school smack dab in the Mid West, I was proud of that. Missouri isn’t the epitome of technology, but none-the-less, the education I received was top-notch.

Taking a very hands-on approach with an emphasis on building things, I had a good portfolio to show potential employers when I graduated. I could talk trade-offs, the good parts, the hard parts – that was a magnificent resource to have as a new grad.

The lesson here is that, even though there is a vast number of resources out there to learn, sometimes the traditional route might be just what you need. I have a ton of respect for the “self-taught” developer – but I quickly found out I was never going to be one of those. I required someone to really dig in and simply teach me, face-to-face.

I Learned The Basics And Went With It

About a year into my education, after I had the basics and foundational knowledge of programming (loops and iteration, variables, data types, control flow, etc.) I began to realize I could teach myself new things. This was probably the most exciting time in my burgeoning career.

Why?

Because now, I was able to go back to those iOS programming books and I knew what the heck I was doing! Objective-C was not so hard to learn anymore, and I was truly able to teach myself iOS development. Those of us who have been doing this for a long time seem to take this for granted a bit, but it’s quite remarkable that in our industry, we can learn virtually any piece of it if we need to.

Did you start in the front end world? You could learn iOS. Did you start in the Java world? You can cross over to Android. Did you start with an emphasis on a back end discipline? You could learn Unity.

The list goes on and on. In fact, this is a common question I get from those who are interested in programming—”Should I learn X or Y?” and the answer is really…learn any of them! You can generally pick up anything else later.

I Interviewed Very Well—How?

Thankfully, I interviewed very well once I had graduated. One of the biggest takeaways I had from feedback was “You are someone our team would like to work with”, or “You seem willing to learn and appear upbeat”, etc.

No matter your personality, introvert or extrovert, you can really get a leg-up in interviews by doing this OneNeatTrick™️: Be honest and human!

The sense I got early on is, even though it’s normal to be nervous for those first interviews, people will gravitate towards the interviewee who smiles, isn’t afraid to say they don’t know an answer, laughs a bit and is generally someone who is having a good time. Even within the context of a high-pressure situation, such as interviewing for a job.

I’ll always remember a remark I had when I landed a coveted internship at my school. There was only one slot open, and it was an $8.50 job to create a web app internally for teachers. I had overheard one of my teachers bring aside two students to offer them an interview. After class, I asked if I could be considered (another lesson – don’t be afraid to be politely assertive for opportunities) and he agreed.

Long story short, I got the job, and it was mentioned that it wasn’t even a close decision. My passion to learn and the excitement to build something isn’t something you can fake, and if you have it – it’s a massive asset.

I Absolutely Sucked at “Test” Exams

Here’s the kicker, though. When I interviewed for my first job out of college, I sucked at all the aptitude tests. In fact, I got what amounts to a “D” grade on most of them. In my area, .NET and SQL are prominent. Many of these tests dealt with writing custom SQL queries, and, well—I wasn’t good at that yet.

So, I didn’t do hot on the four or five tests I took. And yet, in all of those cases save for one, I had job offers. And that is because, I believe, of my previous blurb. They knew I was willing to learn.

In a way, I think this thought would still hold. If I had to do a whiteboard interview with someone watching me, I would freaking melt. I don’t do well in that environment, with someone hovering over me. And it’s weird because that’s not how we work in the real world anyway. But I digress. If you’re a new grad, try not to sweat those tests too much – they are looking more at the person than the programmer.

It goes to show that they don’t really reflect your worth. I had several “big” projects I could show employers in my portfolio, things I had built myself. A can-do attitude armed with a bit of work to show will usually trump a bad showing on those screening tests.

I Thought I Would Be Fired Immediately

I’ll always remember my first task at my first job. I had to add a checkbox that would filter out some data driven by a SQL query. It took me over two weeks.

Looking back, it could’ve been done in two hours.

But this was the first time I was thrown to the beast! A massive, 15-year-old code base. New processes and ways of doing things. I was on a lake trip with my wife towards the end of that two week span, and I remember telling her I wasn’t confident if this career was going to work out for me. I was stressed to the point of wondering how I would provide for my family if I were to be let go.

But, it worked out. A mentor helped me through it, I asked for help, and eventually, it got done. What a roller coaster! I remember being at a pitiful low, immediately rebounding to a resounding high. Every so often, that’s programming for ya. It can humble you quick, and pick you right back up and make you feel like a superhuman creator.

Funny enough, I ran into the person who hired me at that first job a few weeks ago at my son’s wrestling practice. We were reminiscing about the old days, and I mentioned how I was grateful to get the opportunity to work there, even though people were applying with more experience. Long story short, (and allow me to toot my horn here, just momentarily) he mentioned that I quickly became one of their best and most versatile developers. I grew considerably in my time there, and I grew quickly.

If you’re having a hard time initially at your job, keep at it. I promise you, you’re going to learn a lot and improve.

I Learned to Prioritize What I Really Wanted

After nearly three years at that job, I realized my iOS opportunities there were going to be sparse. While me and two other developers were really the only ones who worked on it – I had to be pulled off on other projects, mostly dealing with ASP.NET.

And I quickly realized—iOS was super fun for me. ASP.NET was, well, not.

If I was going to be programming for my career, I wanted to work on what I wanted to work on. And with the rise of remote jobs, I was able to do so. Even following a fairly massive raise, I set out to find a job where I could iOS full time.

If you can, find what really makes you tick and try to work on that. I know it’s not always possible, and look – sometimes we simply have to do some stuff we may not particularly enjoy, but by-and-large if you can work on the tech stack you like, do it!

I Improved In Ways I Did Not Expect

After a decade of coding on the job, you’d think I would say something similar to “I am waaaaay better at programming!” but honestly, I doubt that’s necessarily the case. No doubt, I have learned and improved a lot.

But, the ways I’ve improved the most, and what, I believe, has made me valuable, is not so much that I’m a master, elite programmer – it’s the other stuff I can do that help projects move along and turn out well that I’ve become good at.

I have an annoyingly precise eye for iOS platform features, and what makes a great iOS app. I’ve learned so much about design, and the human beings we make software for. I’ve learned why accessibility is critical, and should never be an afterthought. I know a lot more about trade-offs, and communicating those types of things across not only my team, but other teams, too. I could tell you why something that seems easy is hard, or why something that seems like a good idea might turn out to be a bad one. And, well, I’m always quick to own up to my mistakes because we absolutely all will make them.

More than anything, I’ve become fairly skilled at navigating people, in a sense, and truly displaying a high degree of empathy towards them and their situations. If you can try to see things from their perspective, it goes a long way. Such a tale as old as time, I suppose. Coding is never the hard part, it’s the people, emotions, and egos that you have to learn to work with. In short, we are all human – so be a nice person to work with.

So, have I become a much better programmer in the academic sense? Of course! But also, well - probably not as much as you’d think? I’m certain a new grad from an Ivy League school would run circles around me in LeetCode. I would probably crater literally any whiteboarding interview. But I can do what really counts: write effective and reasonable code, be easy to work with, point out things we could improve on, and help keep everyone in the loop.

Your Family is Most Important

I’ve been extremely lucky, all things considered. I’ve worked at Buffer now for coming on 8 years, and that is quite a long time in the tech world. In some ways, my job experience has been unique from plenty of traditional iOS posts. I’ve worked with one other person virtually this entire time. I have enjoyed that, though, as being a small, nimble team carries plenty of advantages.

More than anything, though, Buffer has let me be there for my family. It has unbeatable work-life balance. With four-day work weeks, I’ve been able to take my kids to school on Fridays. I would otherwise have missed out on this. I cherish all the fun conversations I’ve had with them on the way, and I adore hearing about their school day when I pick them up.

When I went through a major health scare, they let me take off all the time I needed. No questions asked. I’ve taken three weeks off in December for as long as I’ve been there, and it’s my favorite thing to do all year (I’m a Christmas nut 🎅🎄).

Look, changing jobs isn’t easy. Finding “the right one” is even harder. But if you can, try to end up at a place where you are thought of as a person, cliché as it sounds. There is a reason I’ve been at Buffer so long, and one of those key reasons is that it allows me to be around my family a lot, and watch my kids grow up. I work to provide for them, I don’t live to work.

I Look Forward to Doing This For a Long Time

Encouragingly, I look forward to doing this for a long time. Programming never gets old to me, I simply enjoy it. And, when you have to work for a long time, I would submit to you that the simple act of enjoying your career is a massive life hack to living a healthier, balanced life.

There will always be days when you’re in the trenches, and it’s not as fun. But a true interest in your career will eventually carry you through (this advice doesn’t apply to burn out, an entirely different topic). When I’m not at work, I enjoy making my own apps. Of course, I’m writing a five book series over iOS too. I couldn’t do something like that unless the subject endlessly fascinated me. And it does.

Plus, the landscape is constantly changing. How you wrote an iOS app in 2013 is nearly unrecognizable to today. The languages change. The tooling changes. The frameworks come and go. And then, there are major shifts that occur. Presently, that’s GPT-4. Who knows what it will be a decade from now.

For some, that’s stressful. Me? I think it keeps me on my toes.

Final Thoughts

What a decade it has been. I’m thankful to be in this industry, and it’s done so much for me. I can’t help but think that if I could go back and do it again, I’d probably just choose the same route. Here’s to the next decade of programming!

Until next time ✌️

···

Overinspired?

// Written by Jordan Morgan // Mar 8th, 2023 // Read it in about 3 minutes // RE: The Indie Dev Diaries

Without counting it out, try to guess in your head how many app ideas you have written down. Or, better yet - how does your ~Documents/Side Projects folder look these days? Lots of Xcode projects awaiting your attention?

Lately, I’ve started to realize that many things I’d love to make, will never be made. I think this is a result from being overinspired.

The weird thing about being a human today is that we’re dealing with an unprecedented amount of information. No surprise there, certainly. But among that corpus of inputs is an incredible amount of inspiration and success stories.

I’ve started to wonder - am I ingesting too much of a good thing? Do I really need to read another “X person does Y thing and app makes Z dollars?”

To be clear, good for them! They should be proud of all of their accomplishments. Certainly, I’ve made no qualms about being proud of my own (and nor should you!). Yeah, we get it, Spend Stack was acquired like a bajillion years ago. Yet, you can still see that I prominently display that fact on my blog search page. Why? Because I’m proud of it.

The thing is, I’m addicted to seeing everyone’s new app, latest blog post, newest Apple A.P.I., the swankiest U.I. trend, and (fill in the blank). I love all of it. But there has been a downside to all of this, at least for me.

Overinspired: Signs and Symptoms

I’ve started to realize that maybe I just need less of this kind of information. Less podcasts, blogs, a giant folder of apps on my homescreen to emulate, Dribbble, Pinterest boards of UI/UX, etc.

Why? Because I just can’t help myself!

If I read another success story, I have to analyze why “the thing” worked.

If I listen to another podcast, I have to write down why a launch was unsuccessful for fear of repeating the same mistakes.

If I see another beautiful design, I have to store it away to view later.

This problem even extends to reading books, I religiously capture each highlight I come across. Sometimes, this comes at the expense of simply enjoying the book.

What I’m left with lately is a mind that is simply trying to process too much information. Basically, my brain is on Intel, and the world has moved to M2 Pro.

Yet, I truly believe that this is wonderful problem to have! Gone are the days of endlessly searching for any inspiration, yearning to find a like-minded community or talented peers. It’s just that now, it’s too easy! And if you’re like me, it’s all become too much.

I do know some folks to which this problem doesn’t apply. They live on the fire hose of information, and to be honest - it seems good for them to have so many creative inputs. But my personality is too rigid - I don’t keep tabs open, I strictly follow my Things 3 checklist for work, I never leave a Slack message unattended to, I store anything I find to revisit…I could….go on. And for an embarrassingly long time.

I used to think that this was a simple limitation to my personality traits, but now, I simply leverage it. I can become overinspired, and the result is confusion, lack of creative focus, and too many directions I could take things.

So, how have I combat overinspiration? These days, I just really dig into maybe one, or possibly two, of ya’lls gorgeous apps to get U.I. inspiration from. I just want to read one of your success stories and take a lesson from it. I just want to go through one podcast at a time, and really sit with all of the lessons that are found within them.

Everyone is so talented, and everyone does such great work - but I’ve learned that not all of it needs to apply to the things I’m doing.

When I became honest with the fact that I can’t feasibly ingest a lot, I started to (paradoxically) become more focused with my side project work. The less inputs I externally consumed, the narrower my creative outlets became - the more I was able to stop pulling in from the outside and instead push forward my own creative endeavors.

Lately I’ve been thinking a lot about the concept of joy. Kind of random, I know. Though, when I stop and look at the graveyard of apps I’ve started, or SaaS projects I’ve yearned to get to market that I haven’t - I eye them with a bit more levity than I used to. Heretofore, surveying the vast field of half written apps or dust covered .xcodeproject files used to only remind me of what might have been.

But, it’s a irrational thought process to entertain. Is there really some sort of alternate universe where I did all of these things? Would I even want 1,234,343 successful apps!? I don’t think so.

We all have our “box” we need to fill up, but the truth is that if we become super efficient and get more done, we will just figure out we can fill even more into that box than we previously thought. There is no limit.

When I look at things like from this angle, I slowly but surely begin to appreciate the things I’ve done more. Perhaps, more importantly, it really makes me consider the things I actually, truly, want to do next, too. Maybe it’s because I’m getting older and I don’t have as much drive, or - more optimistically, hopefully I’ve grown wiser since becoming a parent, a husband and learning all of the lessons that more time on this earth dishes out to you.

I’ve simply made peace with a simple fact that has really made my stress levels and unrealistic expectations virtually vanish: I should only make things that truly bring me joy. I just love being happy (who knew!), and making things that make me happy, as it turns out, is a healthy practice to pick up.

The great thing about this is, joy is malleable. It can be produced from so many motivations. Maybe joy looks like shipping that app that you think can make the most money, but isn’t exactly a creative masterpiece. I’m actually doing this right now with a basketball/whiteboard/play maker app.

On the other side, the one app that most people probably want from me is the habit/journal/feely-feel good app I made for myself:

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

While that app does bring me joy, certainly - it was, after all, made for that…the thing that brings me most joy right now is scratching a business-y itch of all things. I see this hole in a market, could I fill it? That’s an exciting question for me to answer! And so, I’m answering it.

Is this making any sense? I might be veering into rambling territory, so I digress. Folks, you can’t make every app you want. It’s a fool’s errand. Make the one that makes you happy.

Final Thoughts

Perhaps you’ve become overinspired lately. Try to cut down your creative inputs, and then narrow down your focus to things that only directly apply to what you’re trying to build or goal you’re trying to reach.

Even if you haven’t, and you’re one of those unicorns that can wrangle all of the stuff from our talented community - remember that even you will never ship all of your ideas. You just can’t! You probably have too many awesome ones 😉.

Instead of that being a burden you carry, let it be the blessing that it is! You can be more precise with your time and expectations, surgically choosing what gets to have your focus.

Until next time ✌️

···

ForEach Thought: Volume 2

// Written by Jordan Morgan // Mar 1st, 2023 // Read it in about 3 minutes // RE: ForEach Thought

ForEach Thought() is a kinda newsletter that has three interesting finds in the iOS space, something I’m coding or designing and an interesting API.

Three Byte-Sized Finds

1: More Open Source Projects of Yore
Sophia Teutschler has open sourced the code for a throwback app, Groceries. Remember this one?

Onboarding for an upcoming basketball app.

I love seeing older projects live on like this. Obviously this hits home for me, having recently made Spend Stack open source. If you browse though Groceries source code, you’ll see relics from another time. App delegates, block based logic, swathes of Objective-C, pointers and more! It’s astounding to see just how much of the tech stack has changed to create an iOS app now. New language, new user interface framework - it’s all different.

2: There are a lot of ways to use Shortcuts
I was reading through Culture Code’s announcement for their new Shortcuts in Things 3, and I realized just how many ways there are to invoke a Shortcut in the Apple ecosystem. Even as an iOS developer, I didn’t quite grasp how many entry points there are.

Seriously - can you name them all in your head? Try right now.

As noted in their blog you can perform a Shortcut via…

  • The Shortcuts app
  • Use widgets
  • The menu bar (macOS)
  • The dock (macOS)
  • Keyboard shortcuts (macOS)
  • Spotlight
  • Your voice (Hey Sir…)

And, as of the latest iOS 16 beta, you can add Lock Screen widgets to that list. Verily I say unto you, my friends - there’s never been a better time to dive into Shortcuts. And, with the release of App Intents making the job comically easier than it was before, it’s fairly easy to stand one up.

3: Another Depressing App Rejection Saga
I hate reading these posts, and I hate even more that they happen. Look, I get it, the App Store is huge - but it also should have the necessary resources, and training, to match that surface area. The developers who pour their heart and soul into their apps to release on Apple’s App Store (to the benefit of Apple too, I might add) should never be told the following (emphasis my own):

During our review, we found that this app duplicates the content and functionality of other apps submitted to the App Store, which is considered a form of spam and not appropriate for the App Store.

Not much else to say here other than, yes - let’s absolutely keep the bad apps out, but let’s get the good apps in without issue. Here’s hoping Lucas gets things sorted, and we don’t run into another embarrassing Ice Cubes situation.


Something I'm Working On

I’m closing in on launching my first app since Spend Stack (it’s an app to help basketball coaches share plays). It’s extremely exciting, but this time I’m doing a lot different. Namely, I’m embracing my inner Jordi Bruin and getting it shipped before it’s perfect.

And, wow - that is haaaaaard for me, but I think it’s an important exercise. There are so many rough edges I want to smooth over, but I know the core of the product is there - and I’m not gonna learn more until it’s out in the wild. This is the opposite approach I’ve previously taken, wherein every inch was polished to my liking before it hit the App Store. Missing some features? Sure. But pixel perfect? Yup.

There is a line I’m trying to find, and it’s somewhere between “This looks good enough” and “This looks exactly how I want”, but in that space - there is opportunity to release quicker. So, I’m embracing shipping faster in exchange for the U.I. looking up to my (probably too lofty) standards. I haven’t really tried this and I should. Spend Stack was beautiful, but it also cooked in the kitchen for far too long and never found product market fit. Can I avoid that this time? Hopefully.

Case in point: The onboarding and paywall. I’m not in love with where either of those are at, but I know they are at good enough to launch, and so - I’m going to launch them.

Onboarding for an upcoming basketball app.

The paywall, in particular, was a huge sticker. I want to make you want it! In the end, and after several iterations and discussions with indie buddies, I realized that a point list of features along with the name is “good enough”, so I’m just rolling with it (for now). It’s a new muscle I’m growing, shipping something I’m not particularly proud of - but that also needs to just get out of the door.

As an aside, if you look at some of the biggest earners in the App Store, almost all of them seem to spend zero effort on a spiffy design for their paywall. Maybe there is something to that? I wouldn’t call any of these designs particularly pretty, but surely they must convert?

Paywalls for several popular apps in the App Store


One Obscure API Observation

Mark Moeykens is always tweeting random, super useful SwiftUI tips. Here he is coming in clutch with a nifty solution I wouldn’t have immediately thought of. Here’s the scenario: You browse through SF Symbol’s expansive catalog of iconography, and you realize you want a particular symbol with a badge. The new multicolor rendering icons are perfect for this:

SF Symbols with badging

But if you can’t find it, are you really out of luck? He shares a little “obvious-but-yeah-that-does-work!” methods to get a nice little badge, no matter the icon you use by simply applying another SF Symbol as an overlay to the original one:

Image(systemName: "cup.and.saucer.fill")
    .overlay(alignment: .bottomTrailing) {
        Image(systemName: "plus.circle.fill")
            .symbolRenderingMode(.multicolor)
            .imageScale(.small)
            .background(.background)
            .clipShape(Circle())
            .offset(x: 4, y: 4)
    }

Until next time ✌️

···

Building For Voice Control

// Written by Jordan Morgan // Feb 23rd, 2023 // Read it in about 4 minutes // RE: Accessibility

Voice Control is a groundbreaking accessibility technology that Cupertino & Friends™️ have made available on iOS devices. It made its debut with iOS 13, and it allows you to do…well, basically everything on your iPhone using only your voice.

For real - try it right now! Hold up your device and simply ask Siri to turn it on, “Hey Siri, turn on Voice Control.

let concatenatedThoughts = """

If it's your first time using Voice Control, iOS might have to download a one time configuration file before it's ready to use. Voice Control is also available on macOS, where it may need to perform the same setup.

"""

Once it’s activated, you pretty much say “verb-noun” actions to navigate and use iOS. For example, “Open App Switcher” or “Go Home.” If you’re unsure of what you can do, you can even say “Show Commands” or “Show me what to say” for some hints.

Voice Control has three primary display modes. For anything that is opted into the accessibility engine, you can see:

  1. Item Names: This maps to the element’s accessibilityLabel value.
  2. Numbers: Instead of names, this option simply numbers each option.
  3. Grid: Finally, this route splits out the interface into grid portions, allowing you to focus on an area and drill down into it.

Voice Control's three primary display modes; item names, numbers and grid.

Supercharge AX Testing

One of the things developers may not realize is that in addition to opening up an iPhone to folks who may have motor disabilities, Voice Control is also an incredible cheat code for iOS engineers, too.

Why? Because you can see all of your interface’s accessibility label values instantly!

This makes it trivial to see where you may have missed something. If you’re unfamiliar with accessibility programming on iOS, the accessibility label value is one of, if not the most, important properties to know about.

The item names option is the default mode, so you’ll be able to take stock of things quickly. These days, this is my ideal way to test out things for Voice Over. If you find that your accessibility label isn’t quite right for Voice Control, there’s API to change it too:

// In UIKit
open var accessibilityUserInputLabels: [String]!

// In SwiftUI
.accessibilityInputLabels([Text])

This array of strings can help Voice Control respond to whatever you use in there. Perhaps most importantly, that first string in the array will supersede any accessibility label value for display purposes.

Consider the ever-present cog icon for settings. If you had one for a video editing app, it might look like this:

struct SettingsButton: View {
	var body: some View {
		Button {
			openAudioSettings()
		} label: {
			Image(systemName: "gear.circle")
		}
		.accessibilityLabel("Audio Levels and Mixing Settings")
	}
}

But, saying “Open audio levels and mixing settings” might be a bit much. Not to mention, it’ll crowd screen real estate with Voice Control. So, this is where you may turn to accessibilityInputLabels to do two things:

  1. Shorten what someone needs to say.
  2. Give it other words that Voice Control can respond to.

Given this information, maybe you’d tack on something like this:

struct SettingsButton: View {
	private let axVoiceControlCommands: [Text] = [
		Text("Audio Settings"),
		Text("Settings"),
		Text("Audio Levels"),
		Text("Mixing Settings")
	]

	var body: some View {
		Button {
			openAudioSettings()
		} label: {
			Image(systemName: "gear.circle")
		}
		.accessibilityLabel("Audio Levels and Mixing Settings")
		.accessibilityInputLabels(axVoiceControlCommands)
	}
}

Now, someone could say any of those things - whatever is most obvious and intuitive to them. Commands like “Tap Audio Levels” or “Open Settings” would work with Voice Control.

Challenges

Recently at Buffer, I’ve been improving our Voice Control experience. It mostly just worked, as well it should - because again, that means we’re vending accessibility labels where we should be. That said, I did hit a few bumps in the road.

Similar Item Names
Here’s an interesting one - I had a few places where I had identical accessibility label values. Here, “Create a post for this time” shows over and over:

Voice Control showing a list in a calendar where each label is the same.

I started to wonder if this was an issue. It turns out Photos has the same “problem”, and I wondered how they handled it. It turns out that the answer is…Voice Control solves it.

When you say something that displays multiple times (below, “Tap Live Photo”) - iOS will disambiguate things by hot swapping the display style to numbers:

Voice Control showing in Photos on iOS.

Clever.

Long, Long Names
Further still, there are places where I didn’t have an obvious string to use to interact with the interface. In our new Ideas experience in Buffer, an item in the grid could have just about anything in it. Think of them as social media posts. In this case, the text of their idea didn’t feel very friendly to speak out loud - and this was doubly (triply!) so if the text was a URL.

In this case, I decided to use a numbered system. It’s enough to make things unique, and it also makes it obvious and easy to open things. The right side image is what showed by default, and the left side is the direction I ended up going:

Voice Control showing long names switched out for numbers to open items in a grid.

Sidebar: Gotta love testing data. Yolo!

No API to Detect Voice Control
And finally, this was the sticker for me. Unlike…literally every single accessibility API on iOS…there is no way to know when Voice Control is in use! This proved problematic for places where we had code like this:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
	if indexPath.row == TheOneWithTheSwitchInIt {
		if UIAccessibility.isVoiceOverRunning {
			// Perform some action that toggling
			// The UISwich would normally do
		}
	}  
}

Why would we have code like this? Well, for some table cells - there are UISwitch controls in them. It’s a standard U.X. pattern seen all over iOS. Our implementation reasons that people will not tap the cell to interact with them, but rather interact with the switch.

However, with Voice Over, that’s exactly what we want. The problem is that Voice Control won’t work with this setup. Look specially at the “Pause Queue Off” cell:

Voice Control showing options in a settings view on Buffer for iOS.

So if the user says “Tap Pause Queue Off”, the logic above fires - but the check for Voice Over means that nothing happens. It’s important to note here that Voice Over and Voice Control are mutually exclusive, you can only be using one or the other.

If there were simply API to check for Voice Control’s state, this wouldn’t be an issue. I wish Apple had something like this:

UIAccessibility.isVoiceControlRunning

But alas, it does not - making issues like these harder to solve than they should be.

Final Thoughts

Voice Control is crazy cool - it’s one of those technologies that Apple introduced and I immediately thought, “Wow, they are ahead of everyone else.” But there are some gotchas, it still baffles me that you’re unable to detect if it’s running at the API level. But, as they say in Cupertino, I guess I’ll file a radar.

However, it’s one of the single best ways to test your own Voice Over implementations. And, most importantly, it opens up iOS to many more people who might not otherwise be able to use their iPhones to their fullest potential. That can’t thought of as anything other than a massive win.

Until next time ✌️

···