The iOS technology stack you need to create an indie app in 2024

Volodymyr Dudchak
Arcush Tech
Published in
7 min readDec 10, 2023

In this article, I will try to cover the technology stack that you need to develop an iOS app in 2024. Most of the technologies discussed here are what I’ve used to build Arcush — a simple yet powerful daily planner. Even though the app is not that big and complex, I still had to use plenty of instruments to create it. Let’s dive in.

UI frameworks

Should you learn UIKit in order to build an iOS app in 2024, or is it enough to master SwiftUI? Since its introduction in 2019, SwiftUI has changed a lot, and is still evolving rapidly. Today Apple allows you to build your app entirely on SwiftUI: there’s no need to create Main.storyboard or AppDelegate.swift, you can now specify an entry point to your app with a simple SwiftUI structure conforming to the App protocol—and you’re good to go. Plus, with the new Observation framework, it is easier than ever to bind your model to your view (we’ll discuss it more in the Architecture section). Navigation stack, sheets, lists, buttons, tab bars, texts; a simple layout that fits one screen and view modifiers that allow you to easily change font and color; animations that you can attach to the view and change the way it appears or disappears — all these things work, and do so beautifully with a minimum effort required.

However, as soon as you need something more complex and Apple-provided views don’t cover your needs, SwiftUI stops being your friend. For example, in my app, one of the most complex views from the user interaction perspective is the one that mimics iOS native calendar behaviour:

Calendar view

I’ve tried to implement it in SwiftUI, but it did not work for me. Even with one of the most simple things — the long-press gesture—it is almost impossible to get the exact location using the SwiftUI. View positioning is another headache: you won’t be able to use VStack or List to arrange multiple views in the provided example, so the only viable option will be using the .offset modifier. Even pagination will be a problem, since there is no correspondence to UIPageViewController in the SwiftUI world yet.

But all things aside, SwiftUI is production-ready, and you can start creating your next app in 2024 using SwiftUI alone. But it still makes sense to get yourself familiar with UIKit and be ready to use it when SwiftUI lets you down. Taken the SwiftUI-UIKit interoperability, you should be able to use SwiftUI View components inside the UIKit and vice versa with ease.

Architecture

If you choose SwiftUI to implement your app’s UI, your first choice will definitely be ViewModel-based architecture, or MVVM. You can say it even comes out of the box with SwiftUI — you need to pass some sort of ObservedObject (or Observable is you are developing for iOS 17+) to the view to be able to dynamically propagate your model data into the view and react to the user actions. But MVVM is mostly about view-to-model bindings and it doesn’t help you answer all the other questions you might have while building the app. For example, it doesn’t constrain you on how to build the model layer, where to put your networking code, where the persistence should happen, or how to propagate data back to the view model from the outside world (whether it’s a network request or changes to the user data persisted in iOS—from Photo library, Calendars, etc.). You would rather need to answer those questions yourself or choose a more advanced architecture that will help you design your app in a testable and clear way.

One of the examples of such architecture is Clean architecture, which allows you to clearly separate your presentation, domain, and data access layer. Here is an example of such architecture on github, which uses Combine to bind views to the AppState. This is a good example of unidirectional architecture, which basically means that the app’s data always flows in a single direction (from View to Interactor to AppState, and back to the View using bindings), contrary to the MVVM, where View communicates with ViewModel directly and gets updated through the bindings to ViewModel. Another benefit of this architecture is that it helps you design you data access layer, with introduction of repositories, so it would be clear where to put all your code that interacts with an outside world.

Another family of unidirectional architectures is Redux-based: with such, you’ll have a single state object that represents the actual state of you app, and all the mutations could happen only through the actions you send to the store, with a Reducer responsible for handling those actions and changing the app state. One of the most brilliant implementations of the Redux architecture is The Composable Architecture—and this is what I’ve chosen for my app. Its creators have recently released the 1.0 version, and they have a brilliant series of episodes explaining the architecture in detail, so you definitely should consider it for your next app. One thing that I like the most about this approach is that it allows you to run your app in any state you want: just set up the initial state—and immediately view any screen of your app, no matter how hidden it is. Besides, it allows you to fully test your app’s business logic, but even if you don’t want to spend your time writing tests, the clean separation of your application layers that this architecture offers helps you maintain the app’s code in the long run.

Data persistence

At the last WWDC, Apple introduced its new persistence framework — SwiftData. So if you plan to develop for iOS 17+ and you’re brave enough to try something new (and find bugs that Apple hasn’t fixed yet), you can definitely choose it for your data persistence. Even though it may be tempting to simply decorate your entities with Model macros and use it directly it your SwiftUI views through the model context environment, I wouldn’t recommend doing so: it will be much harder to replace real model entities with mock data to show the UI in the predefined state or to replace the persistence layer entirely if you decide to do so in the future. But with a proper separation of your data layer from the view layer, in can be a good option to consider for your next app.

CoreData is another option to consider if you want to use something Apple is provided for you. Even though SwiftData seems to be a long-term replacement for CoreData, it’s still something you may want to use, especially if you’re going to support systems prior to iOS 17. Despite all its ugliness, CoreData works pretty well for me, and with NSPersistentCloudKitContainer, it’s really easy to share user data between devices with the same Apple ID (SwiftData also provides this possibility). And it’s more than likely that Apple will support it for many years (they’ve at least presented some updates for CoreData this year, unlike Combine, which wasn’t mentioned on WWDC since its introduction).

At last, if you don’t want to rely on Apple-provided tools, you can always create your own SQLite storage. For this approach, I would definitely recommend checking out GRDB library — a great toolkit to work with SQLite databases that allows you to relatively easy convert plain Swift structures to database tables.

Networking

The choice of tools for networking depends on you server implementation, but for the simplest scenarios, URLSession will get you covered. If you need to do something more complex (say uploading a multipart data) and you don’t want to write that code yourself, you can try Alamofire, which is more high-level and offers a range of features that may cover your use cases. Even though Alamofire has existed for years, it is still well-supported. And if you need a full control over the networking, you can always use something more low-level like swift-nio. If you’re an indie developer and want to create your own server but don’t want to learn a new language, you can try Vapor to implement your backend in Swift. Vapor also includes many tools for server-client communication like WebSockets.

Multithreading

Swift structured concurrency is definitely the future of the multithreading on iOS, so if you still haven’t gotten the chance to get yourself familiar with it, it’s a good time to fill the gaps in your knowledge. Soon we would need to say goodbye to GCD with its dispatch queues and completions returning values from the asynchronous code in favor of the new async-await syntax. It’s worth mentioning that Combine is also something that Apple won’t support it the long run (and with the introduction of the Observation framework, SwiftUI doesn’t require Combine anymore). Apple-provided async algorithms are intended to replace most of the Combine operators, something worth looking at as well.

Conclusion

SwiftUI is production-ready in 2024, but you would still need UIKit to help you out when SwiftUI gives up on you. The Composable Architecture is definitely the great choice for you next app, and GRDB will allow you to fully control your SQLite database while using familiar Swift structures. Networking is easy even when using native Apple technologies, and Vapor is something you may consider to write your own backend in Swift. Finally, 2024 is a good year to start using structured concurrency.

I haven’t mention a lot of tools you may need to make sure your app behaves as it’s supposed to and your users takes the most out of it, including the ones for crash reporting, testing, analytics, CI/CD, but I will try to cover most of these topics in my next articles. If you are interested in productivity or want to learn more about the indie app I’m working on, visit our non-technical blog.

--

--

Volodymyr Dudchak
Arcush Tech

I'm a passionate iOS / macOS developer. Ex-Lyft, ex-Macpaw, currently developing the daily planner app: https://arcush.com