Andy Res
Andy Res

Reputation: 16043

Initializer `init(:_rowContent:)` requires that `Type` confirm to `Identifiable`

I am following the KMM tutorial and was able to successfully run the app on Android side. Now I would like to test the iOS part. Everything seems to be fine except the compilation error below. I suppose this must be something trivial, but as I have zero experience with iOS/Swift, I am struggling fixing it.

error

My first attempt was to make RocketLaunchRow extend Identifiable, but then I run into other issues... Would appreciate any help.

xcode version: 12.1

Full source:

import SwiftUI
import shared

func greet() -> String {
    return Greeting().greeting()
}

struct RocketLaunchRow: View {
    var rocketLaunch: RocketLaunch

    var body: some View {
        HStack() {
            VStack(alignment: .leading, spacing: 10.0) {
                Text("Launch name: \(rocketLaunch.missionName)")
                Text(launchText).foregroundColor(launchColor)
                Text("Launch year: \(String(rocketLaunch.launchYear))")
                Text("Launch details: \(rocketLaunch.details ?? "")")
            }
            Spacer()
        }
    }
}

extension RocketLaunchRow {
   private var launchText: String {
       if let isSuccess = rocketLaunch.launchSuccess {
           return isSuccess.boolValue ? "Successful" : "Unsuccessful"
       } else {
           return "No data"
       }
   }

   private var launchColor: Color {
       if let isSuccess = rocketLaunch.launchSuccess {
           return isSuccess.boolValue ? Color.green : Color.red
       } else {
           return Color.gray
       }
   }
}

extension ContentView {
    enum LoadableLaunches {
        case loading
        case result([RocketLaunch])
        case error(String)
    }

    class ViewModel: ObservableObject {
            let sdk: SpaceXSDK
            @Published var launches = LoadableLaunches.loading

            init(sdk: SpaceXSDK) {
                self.sdk = sdk
                self.loadLaunches(forceReload: false)
            }

            func loadLaunches(forceReload: Bool) {
                self.launches = .loading
                    sdk.getLaunches(forceReload: forceReload, completionHandler: { launches, error in
                        if let launches = launches {
                            self.launches = .result(launches)
                        } else {
                            self.launches = .error(error?.localizedDescription ?? "error")
                        }
                    })
            }
        }
}

struct ContentView: View {
  @ObservedObject private(set) var viewModel: ViewModel

    var body: some View {
        NavigationView {
            listView()
            .navigationBarTitle("SpaceX Launches")
            .navigationBarItems(trailing:
                Button("Reload") {
                    self.viewModel.loadLaunches(forceReload: true)
            })
        }
    }

    private func listView() -> AnyView {
        switch viewModel.launches {
        case .loading:
            return AnyView(Text("Loading...").multilineTextAlignment(.center))
        case .result(let launches):
            return AnyView(List(launches) { launch in
                RocketLaunchRow(rocketLaunch: launch)
            })
        case .error(let description):
            return AnyView(Text(description).multilineTextAlignment(.center))
        }
    }
}

Upvotes: 5

Views: 2489

Answers (2)

gswierczynski
gswierczynski

Reputation: 4163

If your model class already has id field you can write an extension like this:

// Swift

extension ModelClass: Identifiable { }

and it will add Identifiable protocol to your model class.

You can also create base model class

// Kotlin

// ListItem has to be a class.
// It will not be possible to create 
// Identifiable extension in swift for kotlin interface
abstract class ListItem { 
    abstract val id: String
}

and reuse it for all your models

// Kotlin

data class ModelOne(
    override val id: String
) : ListItem()

data class ModelTwo(
    override val id: String
) : ListItem()

Then you can write extension like this

// Swift

extension ListItem: Identifiable { }

and it will cover all your models.

Added benefit of this approach is you can use your model classes as item in functions like this

.sheet(item:content:)

Upvotes: 0

instanceof
instanceof

Reputation: 1466

using a List or ForEach on primitive types that don’t conform to the Identifiable protocol, such as an array of strings or integers. In this situation, you should use id: .self as the second parameter to your List or ForEach

From the above, we can see that you need to do this on that line where your error occurs:

return AnyView(List(launches, id: \.self) { launch in

I think that should eliminate your error.

Upvotes: 9

Related Questions