> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/master/todo/tutorials/swiftui/tutorials-model.md).

# Tutorials ⟩ Model

{% tabs %}
{% tab title="files" %}

* Landmark.swift (8)
* ModelData.swift (9)
* Hike.swift (10)
* Profile.swift (11)
  {% endtab %}

{% tab title="8" %}

```swift
import Foundation
import SwiftUI
import CoreLocation

struct Landmark: Hashable, Codable, Identifiable {
    
    /// property required by `Identifiable` protocol;
    var id: Int
    
    var name: String
    var park: String
    var state: String
    var description: String
    var isFavorite: Bool
    var isFeatured: Bool
    
    var category: Category
    
    /// Landmark.Category 
    enum Category: String, CaseIterable, Codable {
        case lakes = "Lakes"
        case rivers = "Rivers"
        case mountains = "Mountains"
    }
    
    // MARK: - image
    
    private var imageName: String
    
    /// loads an image from the asset catalog.
    var image: Image { Image(imageName) }
    
    // MARK: - coordinates
    
    /// for JSON decoding
    struct Coordinates: Hashable, Codable {
        var latitude: Double
        var longitude: Double
    }
    
    private var coordinates: Coordinates
    
    var locationCoordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(
            latitude: coordinates.latitude,
            longitude: coordinates.longitude
        )
    }
    
}

```

{% endtab %}

{% tab title="9" %}

```swift
import Foundation

// 1. import `Combine` framework
import Combine                      // for `ObservableObject`

// 2. conforms to `ObservableObject`
final class ModelData: ObservableObject {
    
    // ⭐️ load initial data from JSON
    // 3. publish properties
    /// An observable object needs to publish any changes to its data,
    /// so that its subscribers can pick up the change.
    @Published var landmarks: [Landmark] = load("landmarkData.json")
    
    /// user profile that persists even after the user dismisses the profile view.
    @Published var profile = Profile.default
    
    /// hike data won't be modified, don’t need to mark it with `@Published`.
    var hikes: [Hike] = load("hikeData.json")
    
    /// dictionary of landmarks (by category name)
    var categories: [String: [Landmark]] {
        Dictionary(
            grouping: landmarks,
            by: { $0.category.rawValue }
        )
    }
    
    /// featured landmarks
    var features: [Landmark] {
        landmarks.filter { $0.isFeatured }
    }
}


/// ⭐️ fetches JSON data from app’s main bundle.
/// - Returns: an instance of a type conforming to `Decodable`
func load<T: Decodable>(_ filename: String) -> T {
    
    // prepare to get data from file
    let data: Data

    // get file URL from app's main bundle
    // (filename -> URL)
    guard
        let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle.")
    }

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

    // Data -> T
    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

```

{% endtab %}

{% tab title="10" %}

```swift
import Foundation

struct Hike: Codable, Hashable, Identifiable {
    
    var id: Int
    var name: String
    var distance: Double
    var difficulty: Int
    var observations: [Observation]

    static var formatter = LengthFormatter()

    var distanceText: String {
        Hike.formatter.string(fromValue: distance, unit: .kilometer)
    }
    
    /// Hike.Observation
    struct Observation: Codable, Hashable {
        var distanceFromStart: Double

        var elevation: Range<Double>
        var pace: Range<Double>
        var heartRate: Range<Double>
    }
}sw
```

{% endtab %}

{% tab title="11" %}

```swift
import Foundation

/// user profile
struct Profile {
    
    var username             : String
    var prefersNotifications = true
    var seasonalPhoto        = Season.winter
    var goalDate             = Date()

    static let `default` = Profile(username: "g_kumar")

    enum Season: String, CaseIterable, Identifiable {
        
        case spring = "🌷"
        case summer = "🌞"
        case autumn = "🍂"
        case winter = "☃️"

        var id: String { rawValue }
    }
}
```

{% endtab %}
{% endtabs %}
