ios – Problem with View Model inside Another View Model

0
268


I have a Home screen that, when a button is pressed, opens a model screen with a form for sending data to the database, inside this model window, as I said, there is a form through NavigationView {, Form { …etc
Inside this form, there is another model window that allows you to select multiple images to upload.
The problem is that because of all this salad, something broke and I just can’t understand what exactly, maybe someone from the outside will see what the problem is.

I see an error [Missing arguments for parameters ‘onEnd’, ‘onSelect’ in call] here:

struct AddItemView_Previews: PreviewProvider {
  static var previews: some View {
        AddItemView()

  }
}

When I change it to PhotosView it causes other errors – (also related to needing to change AddItemView to PhotosView) on the Home screen (which has a button that opens AddItemView which is the problem)

Big Thanks!

All Stuff:

AddItemView

enum Mode {
  case new
  case edit
}

enum Action {
  case delete
  case done
  case cancel
}

struct AddItemView: View {
@Environment(\.presentationMode) private var presentationMode
@State var presentActionSheet = false

// MARK: - State (Initialiser-modifiable)

@ObservedObject var viewModel = NewItemView()
var mode: Mode = .new
var completionHandler: ((Result<Action, Error>) -> Void)?

// MARK: - UI Components

var cancelButton: some View {
  Button(action: { self.handleCancelTapped() }) {
    Text("Cancel")
  }
}

var saveButton: some View {
  Button(action: { self.handleDoneTapped() }) {
    Text(mode == .new ? "Done" : "Save")
  }
  .disabled(!viewModel.modified)
}
// MARK: Connecting image-picker View Model
    @StateObject var imagePickerModel: ImagePickerViewModel = .init()
    @Environment(\.self) var env
// MARK: Callbacks
    var onEnd: ()->()
    var onSelect: ([PHAsset])->()
    
var body: some View {
    NavigationView {
        Form {
            Section(header: Text("New Item")) {
                TextField("Title", text: $viewModel.singleitem.title)
                TextField("Description", text: $viewModel.singleitem.description)
            }
            
            Section(header: Text("Author")) {
                TextField("Author", text: $viewModel.singleitem.author)
            }
            
            if mode == .edit {
                Section {
                    Button("Delete item") { self.presentActionSheet.toggle() }
                        .foregroundColor(.red)
                }
            }
            
            let deviceSize = UIScreen.main.bounds.size
            VStack(spacing: 0) {
                
                HStack {
                    
                    Text("Select Image")
                        .font(.callout.bold())
                        .frame(maxWidth: .infinity, alignment: .leading)
                    Button {
                        onEnd()
                    } label: {
                        Image(systemName: "xmark.circle.fill")
                            .font(.title3)
                            .foregroundColor(.primary)
                    }

                    
                }
                .padding([.horizontal, .top])
                .padding(.bottom, 10)
                
                ScrollView(.vertical, showsIndicators: false) {
                    
                    LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 10), count: 4),
                        spacing: 12) {
                        
                        ForEach($imagePickerModel.fetchedImages){$imageAsset
                            
                            in
                            // MARK: Grid Content
                            GridContent(imageAsset: imageAsset)
                                .onAppear {
                                    
                                    if imageAsset.thumbnail == nil {
                                        
                                        // MARK: Fetching thumbnail image
                                        let manager = PHCachingImageManager.default()
                                        manager.requestImage(for: imageAsset.asset, targetSize: CGSize(width: 100, height: 100), contentMode: .aspectFill, options: nil) { image,
                                            
                                            _ in
                                            imageAsset.thumbnail = image
                                            
                                        }
                                    }
                                    
                                }
                            
                        }
                        
                    }
                        .padding()
                }
                .safeAreaInset(edge: .bottom) {
                    
                    // MARK: Add button
                    Button {
                        
                        let imageAssets = imagePickerModel.selectedImages.compactMap {
                            
                            ImageAsset -> PHAsset? in
                            return ImageAsset.asset
                            
                        }
                        onSelect(imageAssets)
                    } label: {
                        Text("Add\(imagePickerModel.selectedImages.isEmpty ? "" : "\(imagePickerModel.selectedImages.count)Images")")
                            .font(.callout)
                            .fontWeight(.semibold)
                            .foregroundColor(.white)
                            .padding(.horizontal, 30)
                            .padding(.vertical, 10)
                            .background {
                                
                                Capsule()
                                    .fill(.blue)
                                
                            }
                      }
                    .disabled(imagePickerModel.selectedImages.isEmpty)
                    .opacity(imagePickerModel.selectedImages.isEmpty ? 0.6 : 1)
                    .padding(.vertical)
                  }
            }
            .frame(height: deviceSize.height / 1.8)
            .frame(maxWidth: (deviceSize.width - 40) > 350 ? 350 : (deviceSize.width - 40))
            .background {
                
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .fill(env.colorScheme == .dark ? .black : .white)
                
            }
            // MARK: Since its an overlay view
            // Making it to take full screen space
            .frame(width: deviceSize.width, height: deviceSize.height, alignment: .center)
            
         }
    }
    
    
      // MARK: Grid Image Content

      
    .navigationTitle(mode == .new ? "Item" : viewModel.singleitem.title)
    .navigationBarTitleDisplayMode(mode == .new ? .inline : .large)
    .navigationBarItems(
      leading: cancelButton,
      trailing: saveButton
    )
    .actionSheet(isPresented: $presentActionSheet) {
      ActionSheet(title: Text("Are you sure?"),
                  buttons: [
                    .destructive(Text("Delete item"),
                                 action: { self.handleDeleteTapped() }),
                    .cancel()
                  ])
    }
      
  
    
}

// MARK: - Action Handlers

func handleCancelTapped() {
  self.dismiss()
}

func handleDoneTapped() {
  self.viewModel.handleDoneTapped()
  self.dismiss()
}

func handleDeleteTapped() {
  viewModel.handleDeleteTapped()
  self.dismiss()
  self.completionHandler?(.success(.delete))
}

func dismiss() {
  self.presentationMode.wrappedValue.dismiss()
}
    
func GridContent(imageAsset: ImageAsset)-> some View {
            
            GeometryReader { proxy in
                
                let size = proxy.size
                ZStack {
                    
                    if let thumbnail = imageAsset.thumbnail {
                        
                        Image(uiImage: thumbnail)
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: size.width, height: size.height)
                            .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
                        
                    } else {
                        
                        ProgressView()
                            .frame(width: size.width, height: size.height, alignment: .center)
                        
                    }
                    
                    ZStack {
                        
                        RoundedRectangle(cornerRadius: 8, style: .continuous)
                            .fill(.black.opacity(0.1))
                        Circle()
                            .fill(.white.opacity(0.25))
                        Circle()
                            .stroke(.white, lineWidth: 1)
                        
                        if let index = imagePickerModel.selectedImages.firstIndex(where: { asset
                            in
                            asset.id == imageAsset.id
                        }) {
                            
                            Circle()
                                .fill(.blue)
                            
                            Text("\(imagePickerModel.selectedImages[index].assetIndex + 1)")
                                .font(.caption2.bold())
                                .foregroundColor(.white)
                            
                        }
                        
                    }
                    .frame(width: 20, height: 20)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
                    .padding(5)
                    
                }
                .clipped()
                .onTapGesture {
                    // MARK: Adding / Removing Asset
                    withAnimation(.easeInOut) {
                        
                        if let index = imagePickerModel.selectedImages.firstIndex(where: { asset in
                            
                            asset.id == imageAsset.id
                            
                        }) {
                            // MARK: Remove and Update Selected Index
                            imagePickerModel.selectedImages.remove(at: index)
                            imagePickerModel.selectedImages.enumerated().forEach {
                                
                                item in
                                
                                imagePickerModel.selectedImages[item.offset].assetIndex =
                                item.offset
                                
                            }
                            
                        } else {
                            // MARK: Add new
                            var newAsset = imageAsset
                            newAsset.assetIndex = imagePickerModel.selectedImages.count
                            imagePickerModel.selectedImages.append(newAsset)
                             
                        }
                        
                    }
                }
            }
            .frame(height: 70)
        }
    
}


struct AddItemView_Previews: PreviewProvider {
  static var previews: some View {
        AddItemView()

  }
}

extension View {
  func endTextEditing() {
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                    to: nil, from: nil, for: nil)
  }
}

ImagePickerViewModel

class ImagePickerViewModel: ObservableObject {
    
    // MARK: Properties
      
    @Published var fetchedImages: [ImageAsset] = []
    @Published var selectedImages: [ImageAsset] = []
    // MARK: Fetching Images
    
      init() {
          fetchImages()
      }
      
      func fetchImages() {
          let options = PHFetchOptions()
          // MARK: Need modify after finishing
          options.includeHiddenAssets = false
          options.includeAssetSourceTypes = [.typeUserLibrary]
          options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
          PHAsset.fetchAssets(with: .image, options: options).enumerateObjects { asset, _, _ in
              let imageAsset: ImageAsset = .init(asset: asset)
              self.fetchedImages.append(imageAsset)
          }
          
      }
    
}

PhotosView

struct PhotosView: View {
    // MARK: Picker properties
    @State var showPicker: Bool = false
    @State var pickedImages: [UIImage] = []
    
    var body: some View {
        NavigationView {
            
            TabView {
                
                ForEach(pickedImages, id: \.self) { image in
                    
                    GeometryReader { proxy in
                        
                        let size = proxy.size
                        Image(uiImage: image)
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: size.width, height: size.height)
                            .cornerRadius(15)
                        
                    }
                    .padding()
                }
                
            }
            .frame(height: 450)
            // MARK: Swift UI Bug

            .tabViewStyle(.page(indexDisplayMode: pickedImages.isEmpty ? .never :  .always))
            .toolbar {
                
                Button {
            
                    showPicker.toggle()
                    
                } label: {
                    
                Image(systemName: "plus")
                    
                }
                
            }
        
        }
        .popupImagePicker(show: $showPicker) { asset in
            
            // MARK: Example of Kavsoft
            let manager = PHCachingImageManager.default()
            let options = PHImageRequestOptions()
            options.isSynchronous = true
            
            DispatchQueue.global(qos: .userInitiated).async {
                asset.forEach { asset in
                    
                    manager.requestImage(for: asset, targetSize: .init(), contentMode: .default, options: options) { image, _ in
                        
                        guard let image = image else { return }
                     
                        DispatchQueue.main.async {
                            
                            self.pickedImages.append(image)
                            
                        }
                        
                    }
                    
                }
            }
            
        }
    }
}

struct PhotosView_Previews: PreviewProvider {
    static var previews: some View {
        PhotosView()
    }
}

Extension

extension View {
    
    @ViewBuilder
    func popupImagePicker(show: Binding<Bool>, transition: AnyTransition = .move(edge: .bottom), onSelect: @escaping ([PHAsset])->())-> some View {
        
        self
            .overlay {
                let deviceSize = UIScreen.main.bounds.size
                ZStack {
                    // MARK: Bg blur
                    Rectangle()
                        .fill(.ultraThinMaterial)
                        .ignoresSafeArea()
                        .opacity(show.wrappedValue ? 1 : 0)
                        .onTapGesture {
                            
                            show.wrappedValue = false
                            
                        }
                    
                    if show.wrappedValue {
                        
                        AddItemView {
                            
                            show.wrappedValue = false
                            
                        } onSelect: { assets in
                            
                            onSelect(assets)
                            show.wrappedValue = false
                        }
                        .transition(transition)
                    }
                    
                }
                .frame(width: deviceSize.width, height: deviceSize.height)
                .animation(.easeInOut, value: show.wrappedValue)
            }
        
    }
    
} 

ImageAsset

struct ImageAsset: Identifiable {
    var id: String = UUID().uuidString
    var asset: PHAsset
    var thumbnail: UIImage?
    // MARK: Selected Image Index
    var assetIndex: Int = -1

    
}