Right now how you understand the word “Multiplatform” probably depends on whether you’re a Swift/SwiftUI developer or a Kotlin one! At a high level:

  • SwiftUI Multiplatform is a relatively new capability that allows developing client UIs that target iOS and macOS (and potentially also watchOS and tvOS) using a single code base.
  • Kotlin Multiplatform on the other hand is aimed at providing mechanism to share non UI code across a wide range of platforms.

In this article we’re going to outline how to create an example iOS/macOS project that demonstrates how these two emerging capabilities very nicely complement each other! We’re going to make use of the BikeShare Kotlin Multiplatform project that exposes API to access bike share info for a wide range of bike networks around the world (implemented in turn using CityBikes API)….and, in particular, we’re going to consume a Swift Package representing that code (that package also targets iOS and macOS). Some more background on the BikeShare project and how we went about creating the Swift Package can be found in following articles


Creating SwiftUI Multiplatform project in XCode

When you create a new project in XCode there are options to create a variety of “Multiplatform” projects. In our case we’re going to select the “App” option. This will by default create a project that targets iOS and macOS. We’ll go through the wizard accepting the defaults and will name the project BikeShareSwiftUIMultiplatform.

Xcode Create Multiplatform Project


As our apps are going to be making remote API requests we need to also enable the “Outgoing Connections” option for the macOS target.

Xcode: enable outgoing connections


Adding Swift Package representing shared Kotlin Multiplatform code

The github repo where the BikeShare Swift Package is deployed can be found at https://github.com/joreilly/BikeShareSwiftPackage. We’re going to enter this after navigating in XCode to File/Swift Packages/Add Package Dependency…. After providing that url we should see following screen showing that we’re adding the BikeShareKit package.

Xcode: add swift package

This by default will only add the BikeShareKit package to the iOS target. We need to now manually add the package to the macOS taget as well (as shown below).

Xcode: add package to macos target


Having done that we can now take a look at the project navigator and see that we have Shared, iOS and macOS directories. In particular there’s a shared ContentView.swift file that right now includes SwiftUI code that’s used for both targets. We can also see info on the newly added BikeShareKit package including the README file containing some sample SwiftUI code that uses the BikeShare API. As an initial starting point we’ll copy this code in to ContentView.swift. We can now run both iOS and macOS targets and should see bike share info shown for the example network included in the sample code.

Xcode: full project view


And following are the iOS and macOS screenshots when we run that code. The UI is clearly quite basic but hopefully this gets across how easily we can create a SwiftUI Multiplatform project that consumes Swift Package representing our shared Kotlin Multiplatform code. iOS and macOS screenshots


Next Steps

If you want to take this further there’s iOS and macOS samples in the BikeShare repo that build out the UI a little bit more (those projects make use of CocoaPods integration to build Kotlin Multplatform code from source). The following is the UI for example for the macOS client in that repo.

Xcode: full project view

Those samples also include following View Model which can also be dropped in to the shared Swift code for the sample we’ve been building out here.

class BikeShareViewModel: ObservableObject {
    @Published var stationList = [Station]()
    @Published var networkList = [String: [Network]]()
    
    private let repository: CityBikesRepository
    init(repository: CityBikesRepository) {
        self.repository = repository
    }

    
    func fetchNetworkList() {
        repository.fetchGroupedNetworkList(success: { data in
            self.networkList = data
        }) { (result, error) in
            if let errorReal = error {
                print(errorReal)
            }
        }
    }

    func fetchBikeShareInfo(network: String) {
        repository.fetchBikeShareInfo(network: network) { data, error in
            if let stationList = data {
                self.stationList = stationList
            }
        }
    }
}


Featured in Kotlin Weekly Issue #243