AppCode 2023.1 Help

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.

Final app

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.

  1. Launch AppCode and click New Project on the Welcome screen:

    Welcome Screen

    If you have another project open in AppCode at the moment, select File | New project from the main menu.

  2. In the dialog that opens, you see the list of Xcode project templates. Select iOS | Application | App and click Next:

    Create a new project: project templates
  3. On the next page, adjust the general project settings:

    Create a new project: 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.

  4. 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.

New project

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

  1. Add the HotReloading package to your project from Xcode:

    Adding the HotReloading Swift package

    Alternatively, install and run the InjectionIII application.

  2. Add the -Xlinker -interposable flag to the Other Linker Flags section of the project build settings Ctrl+Alt+Shift+S:

    Add a new flag in the project settings
  3. Open the iOSConferencesApp.swift file and in the iOSConferencesApp structure, add the init() 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() } } }
  4. Go to the ContentView.swift file. In ContentView_Previews, add the #if DEBUG block with the injected() method inside and convert struct to class:

    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

  1. Select a device or simulator on the toolbar:

    Select device
  2. Press Shift+F10 or click the Run button.

    If the HotReloading package was added to your project, you will see the following message:

    Hot Reload available

    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:

    Injection connected
  3. Duplicate the Text control, save the file (Ctrl+S), and see the changes on the simulator or device screen:

    SwiftUI preview
  4. If you don't want to save changes manually to update the preview, go to Preferences | Appearance & Behavior | System Settings | Autosave, select the Save files automatically if application is idle for … sec checkbox, and set its value to 1.

Step 3. Create a list

Let's add a List control and adjust its appearance.

1. Rename the view

  1. Place the caret at ContentView, press Shift+F6, type ConferenceList in the highlighted area, and press Enter.

  2. Click the Rename in comments button and select the Comments and strings checkbox.

  3. In the Find tool window that opens, click Do Refactor. AppCode will modify the usages of this symbol everywhere including filenames and comments.

    Rename refactoring
  4. The same way, rename ContentView_Previews to ConferenceList_Previews.

2. Create a surround livee template for SwiftUI elements

  1. Wrap the Text control in a List:

    struct ConferenceList: View { var body: some View { List { Text("Hello, world!") .padding() Text("Hello, world!") .padding() } } }

    The preview will display this change:

    The simple SwiftUI list
  2. Select the List control in the editor, press Ctrl+Shift+A, and find the Save as Live Template… action:

    Create a template from selection
  3. In the dialog that opens, modify the template text:

    $ELEMENT$ {$SELECTION$}
  4. Make the template applicable in Swift declarations and statements (click the Change link in the bottom-left corner of the dialog).

  5. Add an abbreviation for the template, for example, sut, rename the custom template group to SwiftUI, and click OK:

    Modified custom surround template for SwiftUI
  6. 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:

    Surround code with a custom template

3. Change the list appearance

  1. Add a title to the list. To do this, wrap the list in NavigationView and call the navigationBarTitle(_:) method:

    struct ConferenceList: View { var body: some View { NavigationView { List { Text("Hello, world!") .padding() Text("Hello, world!") .padding() }.navigationBarTitle("Conferences") } } }
  2. 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 a VStack 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") } } }
  3. Align both Text controls left using the alignment parameter for the VStack container:

    VStack(alignment: .leading) { // … }

In the end, the list will look as follows:

Redesigned list

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

  1. Download the conferencesData.json file from our repository.

  2. Select the iOSConferences group in the Project tool window, press Alt+Insert, and choose Group.

  3. In the dialog that opens, type Resources, make sure the Create folder, checkbox is selected, and click OK:

    Create a new group
  4. Right-click the Resources group, select Add | Files 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.

  1. In the iOSConferences group, create a group called Model.

  2. Create a new Swift file there (New | Swift File) and call it Data:

    Create a new Swift file
  3. 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)") } }
  4. In the Model group, create the Conference Swift class (New | Swift Type) :

    Add a new Swift type
  5. The Conference class should conform to the Codable and Identifiable 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 }
  6. In Data.swift, introduce the conferencesData variable that will store an array of Conference 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.

  1. Go to the ConferenceList.swift file and pass conferencesData to the List initializer:

    List(conferencesData) {conference in … }.navigationBarTitle("Conferences")

    Now the list displays as many items as the conferencesData array contains:

    The updated list

  2. Finally, replace the Conference and Location 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:

Conferences list

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

  1. In the Project tool window, select the iOSConferences group, press Alt+Insert, and select File from Xcode Template….

  2. In the dialog that opens, select iOS | User Interface | SwiftUI View and click Next:

    Create a new SwiftUI file
  3. On the next page, type the filename — ConferenceDetails, make sure that the iOSConferences group and location are selected, and click Finish.

  4. Enable the interactive preview for the new view in ConferenceDetails_Previews the same way you did it for ConferenceList_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.

  1. In the ConferenceList.swift file, wrap the VStack container in a NavigationLink.

    NavigationLink { VStack(alignment: .leading) { Text(conference.name).font(.headline) Text(conference.location).font(.subheadline) } }
  2. Pass the ConferenceDetails view to the destination parameter of the NavigationLink:

    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:

    Navigation between the list and details

3. Pass conference data to the details view

The ConferenceDetails view should display the detailed information on the conference selected in the list.

  1. Go to the ConferenceDetails view and replace the default Hello, World! text with the conference location:

    struct ConferenceDetails: View { var body: some View { Text(conference.location) } }
  2. 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:

    Create a property from its usage

    As a result, you should have the following code:

    struct ConferenceDetails: View { var conference: Conference var body: some View { Text(conference.location) } }
  3. 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 at ConferenceDetails(), 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:

    Find usages

    In the ConferenceDetails.swift file, pass Conference() as the conference 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)) { … }
  4. Conference() is still highlighted because the Conference class doesn't have any initializers. To add one, place the caret at Conference(), press Alt+Enter, and select Create initializer:

    Create an initializer

    An empty init() method will be added to the Conference 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:

Conference location
  1. 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) } }
  2. 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 convert Date to String. Let's use a new textDates() method for this purpose and call it for the second Text 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':

    Create a method from usage

    An empty textDates() method will be created in the Conference 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 the Date class. Go to Data.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:

    Conference date displayed
  3. Implement the LinkButton control which will display a clickable link to conference websites. In ConferenceDetails.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 with LinkButton:

    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:

    Link preview

5. Adjust the details view appearance

The only thing left now is to enchance the details view layout.

  1. In the ConferenceDetails view, wrap the Text controls and the LinkButton in a VStack 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) } }
  2. Align the VStack content left:

    VStack(alignment: .leading) { // … }
  3. Add the default padding for the VStack container:

    VStack(alignment: .leading) { // … }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) .padding()
  4. 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) }
  5. 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:

    Adjust the details view appearance

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.

Last modified: 24 November 2022