简体   繁体   中英

onAppear SwiftUI iOS 14.4

I would like to display the details of a tourist destination when a destination is selected. Below is the syntax that I created, I call self.presenter.getDetail(request: destination.id) which is in.onAppear, when the program starts and I press a destination, xcode says that self.presenter.detailDestination..like doesn't exist or nil. Even when I insert print ("TEST") what happens is error nil from self.presenter.detailDestination..like

struct DetailView: View {
  @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
  @State private var showingAlert = false
  @ObservedObject var presenter: GetDetailPresenter<
    Interactor<String, DestinationDomainModel, GetDetailDestinationRepository<
        GetDestinationLocaleDataSource, GetDetailDestinationRemoteDataSource,
        DetailDestinationTransformer>>,
    Interactor<String, DestinationDomainModel, UpdateFavoriteDestinationRepository<
        FavoriteDestinationLocaleDataSource, DetailDestinationTransformer>>>
  
  var destination: DestinationDomainModel
  
  var body: some View {
    ZStack {
      if presenter.isLoading {
        loadingIndicator
      } else {
        ZStack {
          GeometryReader { geo in
            ScrollView(.vertical) {
              VStack {
                self.imageCategory
                  .padding(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 0))
                  .frame(width: geo.size.width, height: 270)
                
                self.content
                  .padding()
              }
            }
          }
          .edgesIgnoringSafeArea(.all)
          .padding(.bottom, 80)
          
          VStack {
            Spacer()
            favorite
              .padding(EdgeInsets.init(top: 0, leading: 16, bottom: 10, trailing: 16))
          }
        }
      }
    }
    .onAppear {
      self.presenter.getDetail(request: destination.id)
    }
    .navigationBarBackButtonHidden(true)
    .navigationBarItems(leading: btnBack)
  }
}

extension DetailView {
  var btnBack : some View { Button(action: {
    self.presentationMode.wrappedValue.dismiss()
  }) {
    HStack {
      Image(systemName: "arrow.left.circle.fill")
        .aspectRatio(contentMode: .fill)
        .foregroundColor(.black)
      Text("Back")
        .foregroundColor(.black)
    }
   }
  }
  
  var spacer: some View {
    Spacer()
  }
  
  var loadingIndicator: some View {
    VStack {
      Text("Loading...")
      ActivityIndicator()
    }
  }
  
  var imageCategory: some View {
    WebImage(url: URL(string: self.destination.image))
      .resizable()
      .indicator(.activity)
      .transition(.fade(duration: 0.5))
      .aspectRatio(contentMode: .fill)
      .frame(width: UIScreen.main.bounds.width, height: 270, alignment: .center)
      .clipShape(RoundedCorner(radius: 30, corners: [.bottomLeft, .bottomRight]))
  }
  
  var header: some View {
    VStack(alignment: .leading) {
      Text("\(self.presenter.detailDestination!.like) Peoples Like This")
        .padding(.bottom, 10)
      
      Text(self.presenter.detailDestination!.name)
        .font(.largeTitle)
        .bold()
        .padding(.bottom, 5)
      
      Text(self.presenter.detailDestination!.address)
        .font(.system(size: 18))
        .bold()
      
      Text("Coordinate: \(self.presenter.detailDestination!.longitude), \(self.presenter.detailDestination!.latitude)")
        .font(.system(size: 13))
    }
  }
  
  var favorite: some View {
    Button(action: {
      self.presenter.updateFavoriteDestination(request: String(self.destination.id))
      
      self.showingAlert.toggle()
    }) {
      if self.presenter.detailDestination!.isFavorite == true {
        Text("Remove From Favorite")
          .font(.system(size: 20))
          .bold()
          .onAppear {
            self.presenter.getDetail(request: destination.id)
          }
      } else {
        Text("Add To Favorite")
          .font(.system(size: 20))
          .bold()
      }
    }
    .alert(isPresented: $showingAlert) {
      if self.presenter.detailDestination!.isFavorite == true {
        return Alert(title: Text("Info"), message: Text("Destination Has Added"),
                     dismissButton: .default(Text("Ok")))
      } else {
        return Alert(title: Text("Info"), message: Text("Destination Has Removed"),
                     dismissButton: .default(Text("Ok")))
      }
    }
    .frame(width: UIScreen.main.bounds.width - 32, height: 50)
    .buttonStyle(PlainButtonStyle())
    .foregroundColor(Color.white)
    .background(Color.red)
    .cornerRadius(12)
  }
  
  var description: some View {
    VStack(alignment: .leading) {
      Text("Description")
        .font(.system(size: 17))
        .bold()
        .padding(.bottom, 7)
      
      Text(self.presenter.detailDestination!.placeDescription)
        .font(.system(size: 15))
        .multilineTextAlignment(.leading)
        .lineLimit(nil)
        .lineSpacing(5)
    }
  }
  
  var content: some View {
    VStack(alignment: .leading, spacing: 0) {
      header
        .padding(.bottom)
      description
    }
  }
}

onAppear is called during the first render. That means that any values referred to in the view hierarchy ( detailDestination in this case) will be rendered during this pass -- not just after onAppear .

In your header , you refer to self.presenter.detailDestination..like . On the first render, there is not a guarantee that onAppear will have completed it's actions before you force unwrap detailDestination

The simplest solution to this is probably to only conditionally render the rest of the view if detailDestination exists. It looks like you're already trying to do this with isLoading , but there must be a mismatch of states -- my guess is before isLoading is even set to true.

So, your content view could be something like:

if self.presenter.detailDestination != nil {
   VStack(alignment: .leading, spacing: 0) {
      header
        .padding(.bottom)
      description
    }
} else {
  EmptyView()
} 

This is all assuming that your presenter has a @Published property that will trigger a re-render of your current component when detailDestination is actually loaded.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM