Navigation List View with Local JSON: SwiftUI

Ranga Reddy's photo
Ranga Reddy
·May 14, 2021·

6 min read

Navigation List View with Local JSON: SwiftUI

Subscribe to our newsletter and never miss any upcoming articles

Play this article

If you have clearly observed real-world apps, Navigation Lists are the often used app development. In this article, we are Building Navigation Lists with Local JSON. The JSON we are using is from Rick and Morty API. Let's get started!.

Note: This tutorial assumes you have a basic understanding of SwiftUI and JSON API

Get Started

Create a New SwiftUI Project by naming it as Rick and Morty, and running that should show the Hello World message.

Add JSON

Download the characterData.json from the gist file and copy it to the Project.

Add Item Dialog And in the dialog that appears, select “Copy items if needed” and the Rick and Morty target, and then click Finish.

Create a Data Model

Create a new Swift file named Character.swift . Now, define a Character structure with a few properties matching names of some of the keys in the characterData.json data file. Adding Codable conformance makes it easier to load data into the structure from the data file.

import Foundation

struct RMCharacter: Codable {
    let id: Int
    let name: String
    let status: String
    let species: String
    let gender: String

    struct Origin: Codable {
        let name: String
    }
    let origin: Origin

    struct Location: Codable {
        let name: String
    }
    let location: Location
    let image: String
    let created: String
}

Load JSON

Create a new Swift file named ModelData.swift and copy the following code to the file.

import Foundation

// Initialize here

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}
  1. load(_:) method fetches JSON data with a given name from the app’s main bundle.
  2. The load method relies on the return type’s conformance to the Codable protocol.
  3. Create an array of characters that you initialize from characterData.json.
// Replace with`Initialize here` Comment above
var rmCharacters: [RMCharacter] = load("characterData.json")

load(_:) in the ModelData file is a generic function that can be reusable.

Before moving on, group related files together to make it easier to manage your growing project.

NavigationPane.png

Create a Row View

We are building a row view for displaying details about each character. This row view stores information in a property for the character it displays so that one view can display any character. Later, you’ll combine multiple rows into a list of characters.

Create a new SwiftUI view named CharacterRow.swift and copy the following code.

import SwiftUI
import SDWebImageSwiftUI

struct CharacterRow: View {
    // 1
    var character: RMCharacter

    var body: some View {
        // 2
        HStack {
            // 3
            WebImage(url: URL(string: character.image))
                .resizable()
                .scaledToFit()
                .frame(width: 50, height: 50, alignment: .center)
                .clipShape(RoundedRectangle(cornerRadius: 20.0), style: FillStyle())
            // 4
            VStack(alignment: .leading) {
                Text(character.name)
                    .font(.title3)
                Text(character.species)
                    .font(.footnote)
                    .foregroundColor(.secondary)
            }
            Spacer()
        }.padding()
    }
}

struct CharacterRow_Previews: PreviewProvider {
    static var previews: some View {
        CharacterRow(character: rmCharacters[0])
    }
}
  1. Add a character as a stored property of CharacterRow.
  2. Create an HStack having two views and a Spacer at the end.
  3. WebImage view, which loads images from a URL asynchronously using SDWebImageSwiftUI Package
  4. Create a VStack having details of the character(name and species).
Note: When you add the character property, the preview stops working because the CharacterRow type needs a character instance during initialization. In the previews static property of CharacterRow_Previews, add the character parameter to the CharacterRow initializer, specifying the first element of the `rmCharacters` array.
Note: Before building the app. Change the view in ContentView.

Build and run.

RowView.png

Dynamic Lists

Create a new SwiftUI view named CharacterList.swift and copy the following code.

struct CharacterList: View {
    var body: some View {
        // 1
        List(rmCharacters,id: \.id) { character in
            // 2
            CharacterRow(character: character)
        }
    }
}
  1. rmCharacters collection is passed into a list along with an id property. So, that helps to identify each element uniquely.
  2. Complete the dynamically-generated list by returning a CharacterRow from the closure.

Build and run.

CharacterList.png

Create a Detail View

Create a CharacterDetail View that can show the Character Details and copy the following code to the file.

import SwiftUI
import SDWebImageSwiftUI

struct CharacterDetail: View {
    // 1
var character: RMCharacter

var body: some View {
    List {
      // 2
      Section(header: Text("Mugshot"), content: {
        HStack {
         Spacer()
         WebImage(url: URL(string: character.image))
          .resizable()
          .scaledToFit()
          .frame(width: 150, height: 150, alignment: .center)
          .clipShape(RoundedRectangle(cornerRadius: 52.0), style: FillStyle())
         Spacer()
        }
       })

        // 3
      Section(header: Text("Info"), content: {
       CharacterInfoRow(icon: "hare", title: "Species", value: character.species)
       CharacterInfoRow(icon: "eyes", title: "Gender", value: character.species)
       CharacterInfoRow(icon: "waveform.path.ecg.rectangle", title: "Status", value: character.status)
       CharacterInfoRow(icon: "map", title: "Location", value: character.location.name)
       CharacterInfoRow(icon: "paperplane", title: "Origin", value: character.origin.name)
       })
       }// 4
       .listStyle(GroupedListStyle())
       .navigationTitle(character.name)

    }
}


struct CharacterInfoRow: View {

var icon: String
var title: String
var value: String

var body: some View {

 HStack{
  Image(systemName: icon)
    .foregroundColor(.accentColor)
  Text(title)
    .font(.body)
    .foregroundColor(.accentColor)
  Spacer()
  Text(value)
     }
    }
}

struct CharacterDetail_Previews: PreviewProvider {
    static var previews: some View {
        CharacterDetail(character: rmCharacters[0])
    }
}
  1. Add a character as a stored property of CharacterDetail.
  2. Section that displays the Mugshot of the character.
  3. Section that displays the character info using a CharacterInfoRow SubView.
  4. Configuring list to GroupedListStyle.

Build and run.

CharacterDetail.png

Set Up Navigation Between List and Detail

The list renders properly, but you can’t tap an individual character to see that character’s detail page yet. You add navigation capabilities to a list by embedding it in a NavigationView and then nesting each row in a NavigationLink to set up a transition to a destination view.

Change CharacterList by copying the following code.

NavigationView {
  List(rmCharacters,id: \.id) { character in
   NavigationLink(destination:
      // 1
      CharacterDetail(character: character)) {
      CharacterRow(character: character)
     }
    }// 2
      .navigationTitle("Characters")
}
  1. Inside the list’s closure, wrap the returned row in a NavigationLink, specifying the destination's CharacterDetail view.
  2. Set the title of the navigation bar.

One final step, change the following code as below in ContentView.

struct ContentView: View {
    var body: some View {
        CharacterList()
    }
}

Build and run.

rm-navigation-lists.gif

Hurrah 😃! You have completed building lists using local JSON.

Conclusion

You have learned to create Data Models, loading JSON from a local file, and building Navigation Lists and Navigation to Detail View. F From here, you can replace the local JSON Data with Network Data!

Here is the final code if in case you need it.

If you like to improve your App Development Skills, even more, check out blog.learncodeonline.in. Feel free to connect me on Instagram or Twitter if you have any additional tips or feedback.

I hope you’ve enjoyed this, and Thanks for reading!

Did you find this article valuable?

Support Learn Code Online by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this