Building Cross-Platform Apps With SwiftUI
By: Can Balkaya
Tags: Swift, Mobile, Swiftui, iOS, Programming

Coding Time

Our application will be a very simple article listing app. We’re going to make a really simple application. Maybe I can develop this application further in the articles I’ll write about universal apps in the future.

Final app

Creating a new Xcode project

First of all, let’s create a new Xcode project. We need to create this project as a multiplatform app.

Of course, you can give the project any name you want. After that, the only thing left is to start making your project.

My advice to you while developing a project is to divide your files into folders. While doing this project, you can use folders in the same way I use them.

Building the data model

There will be only one data model in our project. As our application is an article-display application, as you can understand, an article has a data model. This model has four properties: id , title , description , type .

struct Article: Identifiable {



// MARK: - Properties

var id = UUID()

let title: String

let description: String

let type: String

}

Of course, we need objects created with the Article model. We’ll create this data by writing it directly.

let techArticles = [

Article(title: "AirPods Max: This is Expensive", description: "Let's take a closer look at AirPods Max, Apple's last product in 2020.", type: "Tech"),

Article(title: "Mac Apps are Back!", description: "With first Apple Silicon -M1- Macs, many applications that we use on iPhones and iPads come to the Mac App Store! Here is why.", type: "Tech"),

Article(title: "How to Create an Onboarding Screens in Your App", description: "Onboarding screens are very important for new users to fully understand the application and have a smooth user experience.", type: "Tech")

] let scienceArticles = [

Article(title: "Are Apple Products Becoming More Cheaper?", description: "In recent years, when I looked at the price of Apple's newly introduced products, I saw a slight decrease in the price of new products.", type: "Science"),

Article(title: "Limit Properties", description: "Limits can also be evaluated using the properties of limits.", type: "Science"),

Article(title: "Direct Substitution", description: "We can find any limit of a definite function with direct substitution. Let’s find out how we can do this!", type: "Science")

] let designArticles = [

Article(title: "Euler Number", description: "What is the number of e that we usually encounter in calculators? What does it do? Let’s find out what this number is!", type: "Design"),

Article(title: "Introduction of Limits", description: "Now that we have defined the limit, let’s try to better understand the limit by giving an example…", type: "Design"),

Article(title: "Find Limits Using Graphs", description: "Graphs are a great tool for understanding the approaching values. Let’s see how this happens!", type: "Design"),

Article(title: "Find Limits Using Tables", description: "A noteworthy method to understand limits. How, you ask?", type: "Design")

]

Now that our data model and the data sets are ready, let’s create the interface elements to display this data.

Building views

First, let’s create an ArticleView object that shows the properties of a single Article object.

struct ArticleView: View {



// MARK: - Properties

let article: Article



// MARK: - UI Elements

var body: some View {

VStack(alignment: .leading, spacing: 5) {

Text(article.title)

.font(.title)



Text(article.description)

.font(.headline)



Spacer()

}

.padding()

}

}

Now to show these ArticleView elements together, create a new View object. It’s called ArticlesListView .

struct ArticlesListView: View {



// MARK: - Properties

let articles: [Article]



// MARK: - UI Elements

var body: some View {

Text("Hello, TurkishKit!")

}

}

We won’t create the List object directly in the body because when the application is opened on a Mac device, it’s better to set the size accordingly. As you can see below, we adjusted the size of the Mac app with the frame method.

struct ArticlesListView: View {



// MARK: - Properties

let articles: [Article]



// MARK: - UI Elements

var body: some View {

#if os(macOS)

return

view

.frame(minWidth: 400, minHeight: 600)

#else

return view

#endif

}



@ViewBuilder

private var view: some View {

List(articles) { article in

NavigationLink(destination: ArticleView(article: article)) {

ArticleView(article: article)

}

}

.navigationTitle("\(articles[0].type)")

}

}

Now it’s time for the important part: SideBar and TabBar . TabBar and SideBar elements are extremely important for the structure of our application. Usually we divide our applications with these elements. In this app, the TabBar element will also be used for the iPhone version, and the SideBar element will be used for the iPad and Mac devices.

Let’s start with the TabBar element. The only thing we do in this element is arrange our main screens. We use the tag method to position the screens truly.

struct TabBar: View {



// MARK: - UI Elements

var body: some View {

TabView {

ArticlesListView(articles: techArticles)

.tabItem {

Image(systemName: "newspaper.fill")

Text("Tech")

}

.tag(0)



ArticlesListView(articles: scienceArticles)

.tabItem {

Image(systemName: "paperclip")

Text("Science")

}

.tag(1)



ArticlesListView(articles: designArticles)

.tabItem {

Image(systemName: "rectangle.and.paperclip")

Text("Design")

}

.tag(2)

}

.navigationTitle("Articles")

}

}

Let’s move onto the SideBar element. We have a basic View element in the SideBar element, similar to the TabBar element: List . We’ll use the NavigationLink object to orient in the List object.

struct SideBar: View {



// MARK: - UI Elements

@ViewBuilder

var body: some View {

List {

NavigationLink(

destination: ArticlesListView(articles: techArticles),

label: {

Label("Tech", systemImage: "newspaper.fill")

}

)

.tag(NavigationItem.tech)



NavigationLink(

destination: ArticlesListView(articles: scienceArticles),

label: {

Label("Science", systemImage: "paperclip")

}

)

.tag(NavigationItem.science)



NavigationLink(

destination: ArticlesListView(articles: designArticles),

label: {

Label("Design", systemImage: "rectangle.and.paperclip")

}

)

.tag(NavigationItem.design)

}

.navigationTitle("Articles")

.listStyle(SidebarListStyle())

}

}

Develop the code as follows to ensure the correct positioning of the NavigationLink elements.

enum NavigationItem {

case tech

case science

case design

} struct SideBar: View {



// MARK: - Properties

@State var selection: Set<NavigationItem> = [.tech]



// MARK: - UI Elements

@ViewBuilder

var body: some View {

List(selection: $selection) {

NavigationLink(

destination: ArticlesListView(articles: techArticles),

label: {

Label("Tech", systemImage: "newspaper.fill")

}

)

.tag(NavigationItem.tech)



NavigationLink(

destination: ArticlesListView(articles: scienceArticles),

label: {

Label("Science", systemImage: "paperclip")

}

)

.tag(NavigationItem.science)



NavigationLink(

destination: ArticlesListView(articles: designArticles),

label: {

Label("Design", systemImage: "rectangle.and.paperclip")

}

)

.tag(NavigationItem.design)

}

.navigationTitle("Articles")

.listStyle(SidebarListStyle())

}

}

Important: You have to locate the files of the SideBar and TabBar elements as done below — otherwise, problems will occur.

If your files can’t communicate with each other, you may need to edit your Target Memberships. Our project is very small, so you can have the two existing targets open in each file.

Finally, it’s time to edit the ContentView . Here we need to run different elements depending on which device the app is running on. For this, we’ll use tools like #if , which we also used earlier, and then we’ll use the horizontalSizeClass property. Thanks to horizontalSizeClass , we can detect whether the device running the application is an iPad or an iPhone.

struct ContentView: View {



// MARK: - Properties

#if os(iOS)

@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?

#endif



// MARK: - UI Elements

@ViewBuilder

var body: some View {

NavigationView {

#if os(iOS)

if horizontalSizeClass == .compact {

TabBar()

} else {

SideBar()

}

#else

SideBar()

ArticlesListView(articles: techArticles)

#endif

}

}

}

Thus, our application is officially completed!