Swiftui VStack align to top

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 in AppCode using the InjectionIII application.

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:

    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:

  3. On 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.

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

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

Step 2. Enable the interactive preview

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

1. Install and start InjectionIII

  1. Install the latest version of InjectionIII from AppStore or GitHub. Make sure the application file is saved under the Applications folder.

  2. Start InjectionIII. Its icon will appear in the status bar:

  3. On the first InjectionIII run, you will be prompted to select a project directory. In the Finder window that opens, locate the newly created project and click Select Project Directory.

    In case you need to change the project directory later, for example, if you've changed your project location or want to use InjectionIII with another project, select Open Project from the InjectionIII menu.

2. Prepare the project for working with InjectionIII

  1. Add the -Xlinker -interposable flag to the Other Linker Flags section of the project build settings S:

  2. 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[] } } }

    In projects that use the UIKit App Delegate life cycle, add the same code to the application[_:didFinishLaunchingWithOptions:] method in AppDelegate.swift.

  3. 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[] { UIApplication.shared.windows.first?.rootViewController = UIHostingController[rootView: ContentView[]] } #endif }

    The injected[] method reloads the view on code injection. Instead of reloading the view, you can generate a design-time preview [like in Xcode]:

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

3. Run the application with preview

  1. Select a device or simulator on the toolbar:

    To run the application on a real device connected to your Mac, you need to configure your project in Xcode first.

  2. Press F10 or click .

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

  3. Duplicate the Text control, save the file [ S], and see the changes on the simulator or device screen:

  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 F6, type ConferenceList in the highlighted area, and press .

  2. Click 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.

  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:

  2. Select the List control in the editor, press A, and find the Save as Live Template action:

  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:

  6. Now you can use this template to surround your code with SwiftUI elements. Select the code lines that need to be surrounded, press T, and choose the new template from the list:

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] { // }

    To show documentation for a symbol at caret, press Q.

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

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

  2. Select the iOSConferences group in the Project tool window, press N, and choose Group.

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

    If you clear the Create folder checkbox, AppCode will create a group without adding the corresponding folder in the file system. See more in Populating Projects.

  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:

  3. In the newly created file, add the loadFile[] function that we will use to decode the JSON file:

    func loadFile[_ 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] :

  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"]

    You can use the let live template to introduce variables: type let and press . To navigate between the template placeholders, also press .

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:

  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:

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 N, and select File from Xcode Template.

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

  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[] { UIApplication.shared.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:

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 [ ] 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] } }
  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 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 , and select Apply Fix-it:

    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[] { UIApplication.shared.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 , and select Create 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 = "//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

  1. Duplicate 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 , and select Create method 'textDates':

    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:

  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:

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] }

    You can place multiple carets and type your code in several lines at the same time. To do this, place the caret where you want to start typing, press and hold Shift+Alt, and place the caret at the other lines:

  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:

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: 13 December 2021
Enhance performance Use CocoaPods in your project

Video liên quan

Chủ Đề