swiftuiswiftui-listswiftui-foreach

SwiftUI: Passing selected data to a view from button


I'm fairly new to SwiftUI. I'm trying to build an app that is specific for iPads that has a view containing a list of items (clients) where I would like to load the selected client into the detail view within the same view. I wanted to avoid using NavigationStack because I don't want the iPad native UX, I just want to load the selected client data into the ClientDetail() view.

Here is what I have:

Getting client data when tapping

If I set 'selectedClient' explicitly the ClientDetail() view loads properly when tapping through the list. But I want no selection when the ActivityView() first loads into view.

 @State private var selectedClient = Client(name: "Tom", room: "1", time: "0200", status: "Active")

With the below code I have no errors in Xcode but crashes the app with "Implicitly unwrapped nil value in ActivityView.swift"

@State private var selectedClient = Client?(nil)


var body: some View {
        
        ZStack {
            ClientDetail(client: selectedClient!)

ActivityView():

import SwiftUI

struct ActivityView: View {
    @EnvironmentObject var model: ClientViewModel
    @State private var selectedClient = Client?(nil)
    
    var body: some View {
        
        ZStack {
            ClientDetail(client: selectedClient!)
            // ACTIVE CLIENT LIST
            VStack(alignment: .center, spacing: 50) {
                ScrollView {
                    ForEach(model.clients) { client in
                        Button {
                            selectedClient = client
                            print(client)
                        } label: {
                            HStack(alignment: .center, spacing: 20) {
                                Circle()
                                    .fill(Color.accentColor)
                                    .frame(width: 14, height: 14)
                                    .offset(y: -10)
                                VStack(alignment: .leading, spacing: 5) {
                                    Text(client.name)
                                        .customFont(.title3)
                                        .foregroundColor(.white)
                                        .lineLimit(1)
                                    Text("Room \(client.room) · \(client.time)")
                                        .font(.subheadline)
                                        .fontWeight(.medium)
                                        .foregroundColor(.white)
                                        .opacity(0.6)
                                } //: VSTACK
                                Spacer()
                            } //: HSTACK
                            .padding(.horizontal, 30)
                            .padding(.vertical, 18)
                        }
                    }
                }
                
                Spacer()
            } //: VSTACK
            .frame(maxWidth: 294, maxHeight: .infinity)
            .background(CustomColor.lightBlueColor)
            .padding(.leading, 120)
            .frame(maxWidth: .infinity, alignment: .leading)
        }
        
    }
}

struct ActivityView_Previews: PreviewProvider {
    
    static var previews: some View {
        ActivityView()
            .environmentObject(ClientViewModel())
            .previewInterfaceOrientation(.landscapeRight)
    }
}

ClientViewModel():

import Foundation

final class ClientViewModel: ObservableObject {
    @Published var clients = [Client]()
    let testClients = [
        Client(name: "Tyler Goodman", room: "1", time: "2:00pm", status: "active"),
        Client(name: "Jeffery Day", room: "2", time: "2:00pm", status: "active"),
        Client(name: "Troy Roberts", room: "5", time: "1:45pm", status: "active"),
        Client(name: "Helen Carr", room: "3", time: "1:30pm", status: "active"),
        Client(name: "Albert Perry", room: "6", time: "1:30pm", status: "active"),
        Client(name: "Marvin Cross", room: "1", time: "1:15pm", status: "active"),
        Client(name: "Sarah Ryan", room: "2", time: "1:00pm", status: "active"),
        Client(name: "Mason Ortega", room: "4", time: "12:45pm", status: "active")
    ]
    
    init() {
        self.clients = testClients
    }
}

struct Client: Identifiable {
    var id = UUID().uuidString
    var name: String
    var room: String
    var time: String
    var status: String
}

Any pointers on this would be much appreciated. Thank you.

I've tried

@State private var selectedClient = Client?(nil)
@State private var selectedClient = Client? = nil

Get the same resulting error


Solution

  • This line is crashing if selectedClient is nil: ClientDetail(client: selectedClient!) because with !you force unwrap.

    It should be

    @State private var selectedClient: Client? = nil
    

    and in ActivityView:

                if let selectedClient { // unwrap optional client
                    ClientDetail(client: selectedClient) 
                } else {
                    Text("Select a client")
                }