AppCode 2020.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 the local JSON file.

  • Details for each conference.

Final app

Along the way, you'll get familiar with the basic AppCode workflow and useful features that help writing code easier and faster.

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 all the data 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 a list of Xcode project templates. Select iOS | Application | Single View App and click Next:

    Create a new project: project templates

  3. Fill in the following fields:

    • Product Name: your project name which will be also the name of your application. Type iOSConferences.

    • Organization Name: your or your company's name.

    • Organization Identifier: your company's identifier in reverse-DNS format, for example, com.mycompany.

    New project settings

    Your project name and organization identifier together build a bundle identifier — an automatically generated string that will identify your application in the operating system.

  4. Select Swift in the list of languages and SwiftUI in the User Interface field. Make sure that all checkboxes in the dialog are cleared as using tests or Core Data is outside the scope of this tutorial. Click Finish.

    Create a new project: project settings

  5. 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. In the left part of the AppCode window, you see the Project tool window. From here, you can navigate to necessary files, add and delete files and folders, exclude files from indexing or from Xcode project, add files to different targets, and so on.

To show and hide the Project tool window, press ⌘1. For more information, see Project tool window.

Project tool window

Step 2. Enable interactive preview

To preview changes in SwiftUI layouts from AppCode, you can use the InjectionIII application.

1. Install and start InjectionIII

  1. Download InjectionIII from AppStore or from GitHub (version 1.8 or later).

  2. Place the InjectionIII application file to the Application folder.

  3. Start the application. Its icon will appear in the status bar:

    InjectionIII application icon

2. Prepare the project for working with InjectionIII

  1. In the AppDelegate.swift file, add the code for loading the InjectionIII bundle into the application(_:didFinishLaunchingWithOptions:) method:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. #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 return true }
    For more details, refer to the InjectionIII documentation.

  2. In the ContentView.swift file, go to the ContentView_Previews structure and add the injected() method inside the #if DEBUG block. Also, change struct to class for ContentView_Previews:

    class ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } #if DEBUG @objc class func injected() { UIApplication.shared.windows.first?.rootViewController = UIHostingController(rootView: ContentView()) } #endif }
    The injected() method reloads the view on code injection when the application is running in debug mode. Instead of reloading the view, you can have a design-time preview generated on code injection — the same way as Xcode 11 does. In this case, use the previews property of the current PreviewProvider:

    @objc class func injected() { UIApplication.shared.windows.first?.rootViewController = UIHostingController(rootView: ContentView_Previews.previews) }

3. Run the application with preview

  1. Make sure that you have the Debug configuration selected for the current run/debug configuration. To do this, select Run | Edit Configurations from the main menu.

    Debug configuration selected

  2. Run the application ⌃R. To do this, select a device or simulator on the toolbar:

    Select device

    Then, press ⌃R or click the Run button.

    On the first run, you will be prompted to select the project directory in the Finder window that opens.

    Once InjectionIII is connected and the project directory is specified, you'll see the following messages in the Run tool window:

    Injection connected
  3. Try to change some code in your view and see the changes on the simulator or device screen:

Step 3. Create a list

  1. In iOSConferences/ContentView.swift, rename ContentView to ConferenceList using the Rename refactoring: place the caret at ContentView, press ⇧F6, type the new name in the highlighted area, and press :

    The file itself will be renamed as well.

    The same way, rename ContentView_Previews to ConferenceList_Previews.

  2. Rebuild the project to have the interactive preview working after renaming the file: press ⌘F9 or click icons actions compile on the toolbar.

  3. Instead of the Hello, World! text, add a list of two text elements:
    • Select the Text("Hello, World!") line by pressing ⌥↑ and add the List element containing Text("Conference1") instead:

    • Add the second text element by duplicating ⌘D the first one:

    • You may notice that the code misses a space between List and {. Just press ⌥⌘L to reformat the code on the whole page according to the guidelines specified in the Preferences | Editor | Code Style | Swift:

    As a result, you will have the following code:

    struct ConferenceList: View { var body: some View { List { Text("Conference1") Text("Conference2") } } }

    The application screen will look as follows:

    The simple SwiftUI list
  4. Add a title to the list. To do this, wrap the list with the NavigationView element and then call the navigationBarTitle(_:) method for the list:

    struct ConferenceList: View { var body: some View { NavigationView { List { Text("Conference1") Text("Conference2") }.navigationBarTitle("Conferences") } } }
    A simple list with a title
  5. Instead of two list items, let's create one consisting of a title and subtitle. In the title, let's display the conference name and in the subtitle — location. To do this, wrap the two Text elements in a VStack container, change their values, and apply the corresponding font styles to them:

    VStack { Text("Conference").font(.headline) Text("Location").font(.subheadline) }
    Simple SwiftUI list
  6. Align both Text elements left using the alignment parameter for the VStack container:

    VStack(alignment: .leading) { // ... }
    Simple SwiftUI list

Step 4. Load data from JSON

Let's make our list dynamic by loading data from the local conferencesData.json file.

  1. Create a new group Resources where you will keep the JSON file:

    • Right-click the iOSConferences group in the Project tool window and select New | Group.

    • In the dialog that opens, type Resources.

    • Make sure the Create folder checkbox is selected and click OK:

      Create a new group

  2. Add the downloaded JSON to the newly created group:

    • Right-click the Resources group and select Add | Files.

    • Select the conferencesData.json file on your Mac.

    • In the dialog that opens, leave the default settings and click OK:

      Add file

    This file contains a list of iOS conferences. Each conference has a name, link to the official website, start and end dates, and location:

    { "name": "SwiftLeeds", "link": "https://swiftleeds.co.uk/", "start": "2020-10-07", "end": "2020-10-08", "location": "🇬🇧 Leeds, UK" }
  3. Create a new Data.swift file where you will add a function for parsing the JSON data:
    • Create a new group called Model as described above.

    • Select this group, press ⌘N, and select Swift File

    • In the dialog that opens, type Data in the Name field and click OK:

      Add a new Swift file

  4. In the Data.swift file, add a function for reading data from JSON:

    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't parse \(filename): \(T.self):\n\(error)") } }

  5. Create a new class for handling the parsed JSON data:

    • Select the Model group.

    • Press ⌘N and select Swift Type.

    • In the dialog that opens, type Conference in the Name field and click OK:

      Add a new Swift type

    • Declare conformance to the Codable and Identifiable protocols and add a set of properties corresponding to the JSON data:

      class Conference: Codable,Identifiable { var name: String var location: String var start: Date var end: Date? var link: String }

  6. In the Data.swift file, create a variable that will store the parsed data — an array of the Conference objects:

    let conferencesData: [Conference] = loadFile("conferencesData.json")
  7. In the ConferenceList.swift file, pass conferencesData to the List initializer and replace the strings in the Text elements by the values from the loaded JSON::

    List(conferencesData) {conference in VStack(alignment: .leading) { Text(conference.name).font(.headline) Text(conference.location).font(.subheadline) } }.navigationBarTitle("Conferences")

The list of conferences will be displayed:

Conferences list

Step 5. Add the details view

The second view of our application will display the detailed data on each conference.

  1. Create a new SwiftUI file:

    • In the Project tool window, press ⌘N and select File from Xcode Template.

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

      Create a new SwiftUI file

    • On the next page, type the filename — ConferenceDetails, make sure that the iOSConferences group and location are selected, and click Finish.

  2. Instead the default Hello, World! text, add the conference location:

    struct ConferenceDetails: View { var body: some View { Text(conference.location) } }

    Since the conference variable is not defined in the current context, it is highlighted red. To define it directly from its usage, do the following:

    • Place the caret at conference and press ⌥⏎.

    • Select Create local variable 'conference' and press .

    • In the list that appears, select var.

    • To specify the type of the variable, press ⌥⏎ again, select Add explicit type, and start typing Conference.

    • Move the code line up by pressing ⌥⇧↑.

    • Press ⌥⌘L to fix the code formatting.

    As a result, your code will look as follows:

    struct ConferenceDetails: View { var conference: Conference var body: some View { Text(conference.location) } }
  3. Now, the conference parameter is missing in the ConferenceDetails() initializer within the ConferenceDetails_Preview structure. Place the caret at the highlighted code, press ⌥⏎, and select Apply Fix-it. The parameter name and placeholder for its value will appear. Pass Conference() as a parameter:

    struct ConferenceDetails_Previews: PreviewProvider { static var previews: some View { ConferenceDetails(conference: Conference()) } }

    Conference() is highlighted red as the necessary initializer for the Conference class is missing. To add it, with the caret placed at the highlighted code, press ⌥⏎ and select Create initializer. An empty initializer 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" }
  4. Duplicate ⌘D the Text(conference.location) line two times and wrap these three Text elements in a VStack container.

    struct ConferenceDetails: View { var conference: Conference var body: some View { VStack { Text(conference.location) Text(conference.location) Text(conference.location) } } }
  5. Enable the interactive preview for the ConferenceDetails by adding the injected() method for ConferenceDetails_Previews the same way you did for ConferenceList_Previews:

    class ConferenceDetails_Previews: PreviewProvider { static var previews: some View { ConferenceDetails(conference: Conference()) } #if DEBUG @objc class func injected() { UIApplication.shared.windows.first?.rootViewController = UIHostingController(rootView: ConferenceDetails(conference: Conference())) } #endif }

    Rebuild ⌘F9 the application to have the preview working after adding the new file.

By now, leave the ConferenceDetails view as is. Later, you will display here the conference dates and link.

Step 6. Set up navigation between the list and details

  1. In the ConferenceList.swift file, go to the ConferenceList structure and 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 as the destination parameter for the NavigationLink:

    NavigationLink(destination: ConferenceDetails(conference: conference)) { 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:

Display the conference date

In the ConferenceDetails view, the start and end dates should be displayed in format MMMM dd, yyyy - MMMM dd, yyyy.

  1. In the Data.swift file, add a new function dateToString as an extension for the the Date class. This function converts Date to String and changes the date format:
    extension Date { func dateToString() -> String { let format = DateFormatter() format.dateFormat = "MMM dd, yyyy" format.string(from: self) } }

    The code above won't be resolved as the function doesn't return anything. Place the caret withing the highlighted symbol and press ⌥⏎:

    Apply Fix-it

    Press to apply the quick-fix. The return statement will be added:

    extension Date { func dateToString() -> String { let format = DateFormatter() format.dateFormat = "MMM dd, yyyy" return format.string(from: self) } }
  2. Create a method of the Conference class that will return the date of a conference in text format. Open Conference.swift and add the following method after the var link: String line:

    func textDates() -> String { var result = start.dateToString() if let end = self.end { result = "\(result) - \(end.dateToString())" } return result }

  3. In the ConferenceDetails view, replace the value of the second Text element with the conference date returned by the newly created textDates() method:
    VStack { Text(conference.location) Text(conference.textDates()) Text(conference.location) }

    The dates will appear in the preview:

    Dates preview

To make the conference link clickable, you need to add a button and pass a method for opening the URL as its action.

  1. In the ConferenceDetails.swift file, add a new structure named LinkButton that conforms to the View protocol and includes a Button with the Go to official website text:

    struct LinkButton: View { var body: some View { Button() { Text("Go to official website") } } }
  2. Add an action for the Button — opening the conference link with the open(_:) method:

    struct LinkButton: View { var link = "" var body: some View { Button(action: { UIApplication.shared.open(URL(string: self.link)!) }) { Text("Go to official website") } } }
  3. Put the newly created LinkButton in the ConferenceDetails view and pass the conference link as a parameter for the LinkButton initializer:

    struct ConferenceDetails: View { var conference: Conference var body: some View { VStack { Text(conference.location) Text(conference.textDates()) LinkButton(link: conference.link) } } }

    The link will appear in the preview:

    Link preview

Step 8. Adjust the details view appearance

  1. In the ConferenceDetails view, place the content of the VStack container to the top left corner of the screen by adjusting its frame 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) } }
    Adjust the details view appearance

  2. Align the VStack content left by using the alignment parameter:

    VStack(alignment: .leading) { // ... }
    Adjust the details view appearance

  3. Add the default padding for the VStack container using the padding() method:

    VStack(alignment: .leading) { // ... }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) .padding()
    Adjust the details view appearance

  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) }
    Adjust the details view appearance
  5. Put the conference name in the view title by using the navigationBarTitle(_:) method for the VStack container:
    VStack(alignment: .leading) { // ... }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) .padding() .navigationBarTitle(conference.name)

    This change won't be available in the interactive preview. Rerun the application to check it:

    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: 26 June 2020