STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

SwiftUI from hobby to production

この記事は STORES Advent Calendar 2022 の 21日目の記事です。

Intro

Hey this is monolithic-adam. I am an iOS engineer working on STORES Register. This year there have been a lot of big changes and events, I joined STORES, company changed names (hey -> STORES), got a new car, Speaker at iOSDC, staff at DroidKaigi, and many more!

iOSDC!!!

The biggest one for me is going from only touching SwiftUI in my hobby apps to using it in Production here at STORES Inc. So I decided to write a retrospective on it 😊

STORES Register

Before we get into things I wanted to introduce our app STORE Register.

STORES Register is a "merchandise accounting and management" application for iPad that contains the tools any store owner would need to start their own brick and mortar store as well as owners looking to start their own online shop too.

You may have seen a store clerk operating an iPad terminal at a retail store or restaurant, it just might have been STORES Register!

Anyone interested please check out the service site below!

stores.jp

Navigation

I wanted to write about all the things that I found interesting/got stuck on this year and it turns out 99% was SwiftUI navigation and bugs related to it...😅 So here we go!

I started out my SwiftUI journey by refactoring the navigation on our views to stop using the global isPresented and instead use the isActive property on NavigationLink or fullScreenCover(isPresented:). (We still support iOS 14 so we cannot use dismiss yet)

It was a great way to get to know SwiftUI and our app.

Found a lot of really interesting bugs while doing this. For example only in iOS 14.X devices when you have two NavigationLinks on the same view you need to add one with an EmptyView or it will pop to the last view right after you navigate to a view.

// なぜかiOS 14で複数のNavigationLink置いていると勝手にポップするケースあってEmptyViewのNavigationLinkで治る
// https://developer.apple.com/forums/thread/677333
NavigationLink(
    destination: EmptyView(),
    tag: .empty,
    selection: $selection
) {}

FullScreenCover Issues

Also on the modal side using multiple fullScreenCovers instead of just one causes modals not to work only in iOS 14 as well so we need to have one and pass the necessary info to the modal that way. It is really the small things like this that make SwiftUI a whole new adventure.

        .fullScreenCover(isPresented: $isPresentedHome) {
            // HomeView
        }
        .fullScreenCover(isPresented: $isPresentedSetting) {
            // SettingView
        }

.fullScreenCover(item: $sheet) { sheet in
        switch sheet {
        case .home:
            // HomeView
        case .setting:
            // SettingView
        }
 }

Implementing PopToRoot

Our app uses a custom drawer to navigate between views and users had been wanting a way to pop back to the root view. Because of how different navigation is and there is no built in API, when adding popToRoot we had to turn to UIKit with code like below.

public static func popToRootView() {
        findNavigationController(viewController: UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController)?
            .popToRootViewController(animated: true)
    }
static func findNavigationController(viewController: UIViewController?) -> UINavigationController? {
        guard let viewController else {
            return nil
        }

        if let navigationController = viewController as? UINavigationController {
            return navigationController
        }

        for childViewController in viewController.children {
            return findNavigationController(viewController: childViewController)
        }

        return nil
    }

I feel like a lot of SwiftUI users have hit this problem before 😅

This is all before the latest navigation APIs for NavigationStack/NavigationSplitView, so I hope that things are solved there.🤞 My New Years resolution is to play with it during break so I can refactor next year!

Positive Notes

  1. EnvironmentObject
    • Because our app architecture is redux-like there needs to be a central store of data that the app uses and EnvironmentObject is a great way to share the store in your app!
  2. I am speed 🦔
    • Now that I have gotten used to SwiftUI it is way faster to build views. There are about 1000 ways to build something so figuring out what is the best way to tackle a problem is still something new a lot of the time.
  3. Change in thinking
    • Data flows, how to build a list, all these things that have become second nature in UIKit don't translate completely and require a change in thinking so things feel all new again.
    • We use Atomic design principles for our view components and it helps a lot on when and where to split views into their parts/responsibilities
  4. No Storyboards
    • I really don't miss storyboards, reviewing them is next to impossible (especially for big changes), if they break for any reason you have to revert/start over, I just played with a storyboard the other day and it was throwing errors making it impossible to edit it because the views aren't displaying properly while editing.

Summary

I am loving SwiftUI so far. Recently I have only really touched UIKit while moonlighting but aside from some funny navigation bugs it has been great! Compared to hard to debug bluetooth/hardware problems I can handle any weird SwiftUI problems 😘.

Here is to another year of SwiftUI and the changes that come with it!! 🍻