Create a SwiftUI application in AppCode
In this tutorial, you'll create a simple SwiftUI application that shows a list of iOS conferences. The application will consist of two views:
A list of conferences representing data from a local JSON file.
Details on each conference.
Along the way, you'll get familiar with AppCode features and learn how to enable an interactive SwiftUI preview.
Watch our video tutorial and follow the step-by-step instructions below:
Step 1. Create a project
Projects created in AppCode are fully compatible with Xcode and use the same project model. After you create a project in AppCode, you can open and edit it in Xcode and vice versa, and everything will be synchronized.
Launch AppCode and click New Project on the Welcome screen:
If you have another project open in AppCode at the moment, select
from the main menu.In the dialog that opens, you see the list of Xcode project templates. Select Next:
and clickOn the next page, adjust the general project settings:
Product Name: your project name which will also be the name of your application. Type iOSConferences.
Organization Identifier: your or your company's identifier in reverse-DNS format, for example,
com.mycompany
.Make sure that Swift is selected as a programming language and SwiftUI as the user interface.
Leave all checkboxes deselected.
In the Finder window that opens, select a directory where your project will be located.
A new Swift project will be created and immediately opened in AppCode.
Step 2. Enable SwiftUI preview
To enable SwiftUI preview in AppCode, you need to install either the HotReloading Swift Package or the InjectionIII application.
Prepare your project
Add the HotReloading package to your project from Xcode:
Alternatively, install and run the InjectionIII application.
Add the
-Xlinker -interposable
flag to the Other Linker Flags section of the project build settings Ctrl+Alt+Shift+S:Open the iOSConferencesApp.swift file and in the
iOSConferencesApp
structure, add theinit()
method with the code that loads the InjectionIII bundles:import SwiftUI @main struct iOSConferencesApp: App { // Add this method init() { #if DEBUG var injectionBundlePath = "/Applications/InjectionIII.app/Contents/Resources" #if targetEnvironment(macCatalyst) injectionBundlePath = "\(injectionBundlePath)/macOSInjection.bundle" #elseif os(iOS) injectionBundlePath = "\(injectionBundlePath)/iOSInjection.bundle" #endif Bundle(path: injectionBundlePath)?.load() #endif } var body: some Scene { WindowGroup { ContentView() } } }Go to the ContentView.swift file. In
ContentView_Previews
, add the#if DEBUG
block with theinjected()
method inside and convertstruct
toclass
:class ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } #if DEBUG @objc class func injected() { let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene windowScene?.windows.first?.rootViewController = UIHostingController(rootView: ContentView()) } #endif }
Run the application with preview
Select a device or simulator on the toolbar:
Press Shift+F10 or click .
If the HotReloading package was added to your project, you will see the following message:
By default, the interactive preview will work for the files under the home directory. You can specify other directories (separated by commas) in the
INJECTION_DIRECTORIES
environment variable.If you are using InjectionIII, you'll see the following messages once the application is connected:
Duplicate the
Text
control, save the file (Ctrl+S), and see the changes on the simulator or device screen:If you don't want to save changes manually to update the preview, go to Save files automatically if application is idle for … sec checkbox, and set its value to 1.
, select the
Step 3. Create a list
Let's add a List
control and adjust its appearance.
1. Rename the view
Place the caret at
ContentView
, press Shift+F6, typeConferenceList
in the highlighted area, and press Enter.Click and select the Comments and strings checkbox.
In the Find tool window that opens, click Do Refactor. AppCode will modify the usages of this symbol everywhere including filenames and comments.
The same way, rename
ContentView_Previews
toConferenceList_Previews
.
2. Create a surround livee template for SwiftUI elements
Wrap the
Text
control in aList
:struct ConferenceList: View { var body: some View { List { Text("Hello, world!") .padding() Text("Hello, world!") .padding() } } }The preview will display this change:
Select the
List
control in the editor, press Ctrl+Shift+A, and find the Save as Live Template… action:In the dialog that opens, modify the template text:
$ELEMENT$ {$SELECTION$}Make the template applicable in Swift declarations and statements (click the Change link in the bottom-left corner of the dialog).
Add an abbreviation for the template, for example,
sut
, rename the custom template group to SwiftUI, and click OK:Now you can use this template to surround your code with SwiftUI elements. Select the code lines that need to be surrounded, press Ctrl+Alt+T, and choose the new template from the list:
3. Change the list appearance
Add a title to the list. To do this, wrap the list in
NavigationView
and call thenavigationBarTitle(_:)
method:struct ConferenceList: View { var body: some View { NavigationView { List { Text("Hello, world!") .padding() Text("Hello, world!") .padding() }.navigationBarTitle("Conferences") } } }Instead of two list items, create one that consists of a title and subtitle. In the title, display the conference name and in the subtitle — location. To do this, wrap the two
Text
controls in aVStack
container, change their values, and apply the corresponding font styles to them:struct ConferenceList: View { var body: some View { NavigationView { List { VStack { Text("Conference").font(.headline) Text("Location").font(.subheadline) } }.navigationBarTitle("Conferences") } } }Align both
Text
controls left using thealignment
parameter for theVStack
container:VStack(alignment: .leading) { // … }
In the end, the list will look as follows:
Step 4. Load data from JSON
Next, we will add a JSON file with conferences data to our project and display this data in the ConferenceList
view.
1. Add a JSON file to the project
Download the conferencesData.json file from our repository.
Select the
iOSConferences
group in the Project tool window, press Alt+Insert, and choose Group.In the dialog that opens, type Resources, make sure the Create folder, checkbox is selected, and click OK:
Right-click the
Resources
group, select and locate the downloaded JSON in Finder.
2. Parse the JSON file
Let's add a function for parsing the JSON data into an array of Decodable
objects and create a data model for these objects.
In the iOSConferences group, create a group called Model.
Create a new Swift file there (New | Swift File) and call it Data:
In the newly created file, add the
loadFile()
function that we will use to decode the JSON file:func loadFile<T: Decodable>(_ filename: String) -> T { let data: Data guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { fatalError("Cannot find \(filename)") } do { data = try Data(contentsOf: file) } catch { fatalError("Cannot load \(filename):\n\(error)") } do { let decoder = JSONDecoder() let format = DateFormatter() format.dateFormat = "yyyy-mm-dd" decoder.dateDecodingStrategy = .formatted(format) return try decoder.decode(T.self, from: data) } catch { fatalError("Cannot parse \(filename): \(T.self):\n\(error)") } }In the Model group, create the
Conference
Swift class ( ) :The
Conference
class should conform to theCodable
andIdentifiable
protocols and include a set fields that correspond to the parsed JSON data:class Conference: Codable,Identifiable { var name: String var location: String var start: Date var end: Date? var link: String }In Data.swift, introduce the
conferencesData
variable that will store an array ofConference
objects parsed from the JSON file:let conferencesData: [Conference] = loadFile("conferencesData.json")
3. Display conferences in the list
Now when we have the array of conferences saved in the conferencesData
variable, we can pass it to ConferenceList
.
Go to the ConferenceList.swift file and pass
conferencesData
to theList
initializer:List(conferencesData) {conference in … }.navigationBarTitle("Conferences")Now the list displays as many items as the
conferencesData
array contains:Finally, replace the
Conference
andLocation
strings with the real data:List(conferencesData) {conference in VStack(alignment: .leading) { Text(conference.name).font(.headline) Text(conference.location).font(.subheadline) } }.navigationBarTitle("Conferences")
The updated list will look as follows:
Step 5. Add the details view
The second view of our application will display the detailed information on each conference.
1. Create a new SwiftUI file
In the Project tool window, select the
iOSConferences
group, press Alt+Insert, and select File from Xcode Template….In the dialog that opens, select Next:
and clickOn the next page, type the filename — ConferenceDetails, make sure that the iOSConferences group and location are selected, and click Finish.
Enable the interactive preview for the new view in
ConferenceDetails_Previews
the same way you did it forConferenceList_Previews
:class ConferenceDetails_Previews: PreviewProvider { static var previews: some View { ConferenceDetails() } #if DEBUG @objc class func injected() { let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene windowScene?.windows.first?.rootViewController = UIHostingController(rootView: ConferenceDetails()) } #endif }
2. Set up navigation between the views
To set up connection between the views, we will use the NavigationLink functionality.
In the
ConferenceList.swift
file, wrap theVStack
container in aNavigationLink
.NavigationLink { VStack(alignment: .leading) { Text(conference.name).font(.headline) Text(conference.location).font(.subheadline) } }Pass the
ConferenceDetails
view to thedestination
parameter of theNavigationLink
:NavigationLink(destination: ConferenceDetails()) { VStack(alignment: .leading) { Text(conference.name).font(.headline) Text(conference.location).font(.subheadline) } }The list items are now clickable, and the details view opens on tapping each of them:
3. Pass conference data to the details view
The ConferenceDetails
view should display the detailed information on the conference selected in the list.
Go to the
ConferenceDetails
view and replace the defaultHello, World!
text with the conference location:struct ConferenceDetails: View { var body: some View { Text(conference.location) } }The
conference
field is not added yet, that's why its usage is highlighted in red. Apply the Create property 'conference' intention action (Alt+Enter) to create the field:As a result, you should have the following code:
struct ConferenceDetails: View { var conference: Conference var body: some View { Text(conference.location) } }After adding the new field, we need to pass it as a parameter to all the
ConferenceDetails()
initializer calls. Press F2 to navigate to the error-causing code, place the caret atConferenceDetails()
, and press Alt+F7 to find all usages of this initializer.In the Find tool window, you will see all the calls we need to fix. Place the caret at the highlighted code, press Alt+Enter, and select Apply Fix-it:
In the ConferenceDetails.swift file, pass
Conference()
as theconference
parameter in both usages:class ConferenceDetails_Previews: PreviewProvider { static var previews: some View { ConferenceDetails(conference: Conference()) } #if DEBUG @objc class func injected() { let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene windowScene?.windows.first?.rootViewController = UIHostingController(rootView: ConferenceDetails(conference: Conference())) } #endif }In ConferenceList.swift, change the initializer call as follows:
NavigationLink(destination: ConferenceDetails(conference: conference)) { … }Conference()
is still highlighted because theConference
class doesn't have any initializers. To add one, place the caret atConference()
, press Alt+Enter, and select Create initializer:An empty
init()
method will be added to theConference
class. Set initial values for all the properties there, for example:init() { name = "Conference Name" location = "Location" start = Date() end = Date() link = "https://www.google.com" }
Now take a look at the preview. The ConferenceDetails
view displays the location of the conference selected from the ConferenceList
view:
4. Display conference dates and links
Duplicate Ctrl+D the
Text(conference.location)
line two times:struct ConferenceDetails: View { var conference: Conference var body: some View { Text(conference.location) Text(conference.location) Text(conference.location) } }The conference start and end dates should be displayed in the following format:
MMMM dd, yyyy - MMMM dd, yyyy
. To do this, we need to convertDate
toString
. Let's use a newtextDates()
method for this purpose and call it for the secondText
control:struct ConferenceDetails: View { var conference: Conference var body: some View { Text(conference.location) Text(conference.textDates()) Text(conference.location) } }Place the caret at the highlighted code, press Alt+Enter, and select Create method 'textDates':
An empty
textDates()
method will be created in theConference
class. Add the following code there:func textDates() -> String { var result = start.dateToString() if let end = self.end { result = "\(result) - \(end.dateToString())" } return result }This method uses the
dateToString()
utility function that we will add to theDate
class. Go toData.swift
and add the following code there:extension Date { func dateToString() -> String { let format = DateFormatter() format.dateFormat = "MMM dd, yyyy" return format.string(from: self) } }The conference dates now appear in the details view:
Implement the
LinkButton
control which will display a clickable link to conference websites. InConferenceDetails.swift
, add the following code:struct LinkButton: View { var link = "" var body: some View { Button(action: { UIApplication.shared.open(URL(string: self.link)!) }) { Text("Go to official website") } } }Now replace the third
Text
control withLinkButton
:struct ConferenceDetails: View { var conference: Conference var body: some View { Text(conference.location) Text(conference.textDates()) LinkButton(link: conference.link) } }The new control will immediately appear in the preview:
5. Adjust the details view appearance
The only thing left now is to enchance the details view layout.
In the
ConferenceDetails
view, wrap theText
controls and theLinkButton
in aVStack
container and move it to the top-left corner of the screen adjusting its frame's width, height, and alignment:struct ConferenceDetails: View { var conference: Conference var body: some View { VStack { Text(conference.location) Text(conference.textDates()) LinkButton(link: conference.link) }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) } }Align the
VStack
content left:VStack(alignment: .leading) { // … }Add the default padding for the
VStack
container:VStack(alignment: .leading) { // … }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) .padding()Add the bottom padding for the elements within the
VStack
container:VStack(alignment: .leading) { Text(conference.location).padding(.bottom) Text(conference.textDates()).padding(.bottom) LinkButton(link: conference.link).padding(.bottom) }Show the conference name in the view title using the
navigationBarTitle(_:)
method:VStack(alignment: .leading) { // … }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) .padding() .navigationBarTitle(conference.name)Finally, the
ConferenceDetails
view will look as follows:
What's next
You can elaborate this application by making it load the data from the remote YAML file containing the up-to-date list of iOS/macOS conferences. For parsing the data, you can use the Yams library added to the project by means of the CocoaPods dependency manager. See more in the Use CocoaPods in your project tutorial.