1

I try to make UIKit element with replaceable image in it, that I can use like swiftUI element.

I stuck at the moment, when image in UIImageView should be refreshed with imageInBlackBox from ObservableObject. I try'd to set new imageInBlackBox from updateUIView and from imagePickerController after image has been selected. But both of this methods don't update UIImageView, it works only for swiftUI Image element, that I use for test.

How I should make imageView.image be refreshed after imageInBlackBox change?

enter image description here

  //Data model
      struct imageAdderDataHolder: Identifiable, Hashable {
            var id: UUID = UUID()
            var isShowingImagePicker:Bool = false
            var imageInBlackBox:UIImage = UIImage(systemName: "photo")! // image for replace
            var height: CGFloat = 160
            var width: CGFloat = 160
        }
        //Data storage
        class imageAdderData: ObservableObject{
            init() {}
            static let shared = imageAdderData()
            @Published var img1: imageAdderDataHolder = imageAdderDataHolder()
        }
        
        struct simpleadder: UIViewRepresentable{
            @ObservedObject var imageData: imageAdderData = .shared
            let mainView: UIView = UIView()
            var imageView: UIImageView = UIImageView()
            
            func makeUIView(context: Context) -> UIView {
                imageView.image = imageData.img1.imageInBlackBox
                imageView.frame.size.width = imageData.img1.width
                imageView.frame.size.height = imageData.img1.height
                imageView.contentMode = .scaleAspectFit
                mainView.addSubview(imageView)
                return mainView
            }
            
            func updateUIView(_ uiView: UIView, context: Context) {
                imageView.image = imageData.img1.imageInBlackBox // try to replace image
            }
        }
        // swiftui view for test
        struct  photoadder: View {
            @ObservedObject var imageData: imageAdderData = .shared
            var body: some View {
                VStack{
                    HStack{
                        simpleadder()
                            .frame(width: imageData.img1.width, height: imageData.img1.height)
                            .border(Color.black, width:1)
                            .sheet(isPresented: $imageData.img1.isShowingImagePicker, content: {
                                imagePickerUIView(isPresented: $imageData.img1.isShowingImagePicker)
                            })
                        Image(uiImage: imageData.img1.imageInBlackBox) // working element for test
                            .resizable()
                            .aspectRatio(contentMode: ContentMode.fit)
                            .frame(width: imageData.img1.width, height: imageData.img1.height)
                            .border(Color.black, width: 1)
                    }
                    Button("change image") {
                        imageData.img1.isShowingImagePicker = true
                    }
                }
            }
        }
        
        
        struct imagePickerUIView: UIViewControllerRepresentable {
            @ObservedObject var imageData: imageAdderData = .shared
            @Binding var isPresented: Bool
            func makeUIViewController(context:
                                        UIViewControllerRepresentableContext<imagePickerUIView>) ->
            UIViewController {
                let controller = UIImagePickerController()
                controller.delegate = context.coordinator
                return controller
            }
            
            func makeCoordinator() -> imagePickerUIView.Coordinator {
                return Coordinator(parent: self)
            }
            
            class Coordinator: NSObject, UIImagePickerControllerDelegate,
                               UINavigationControllerDelegate {
                
                let parent: imagePickerUIView
                init(parent: imagePickerUIView) {
                    self.parent = parent
                }
                
                func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info:
                                            [UIImagePickerController.InfoKey : Any]) {
                    if let selectedImageFromPicker = info[.originalImage] as? UIImage {
// try to replace image
                        parent.imageData.img1.imageInBlackBox = selectedImageFromPicker
                    }
                    self.parent.isPresented = false
                }
            }
            func updateUIViewController(_ uiViewController:
                                            imagePickerUIView.UIViewControllerType, context:
                                                UIViewControllerRepresentableContext<imagePickerUIView>) {
            }
        }
Davagaz
  • 854
  • 1
  • 10
  • 23

1 Answers1

1

Here is a solution - in representable all views should be created inside makeUIView, because struct can be (and usually does) recreated on parent update (so internal instances will be recreated as well, while made UIKit views life-cycle persisted).

Tested with Xcode 12.4 / iOS 14.4.

demo

Fixed code:

struct simpleadder: UIViewRepresentable{
    @ObservedObject var imageData: imageAdderData = .shared

    func makeUIView(context: Context) -> UIView {
        let mainView: UIView = UIView()               // << create here !!
        let imageView: UIImageView = UIImageView()    // << create here !!
        imageView.image = imageData.img1.imageInBlackBox
        imageView.frame.size.width = imageData.img1.width
        imageView.frame.size.height = imageData.img1.height
        imageView.contentMode = .scaleAspectFit
        mainView.addSubview(imageView)
        return mainView
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        // find needed view in run-time
        if let imageView = uiView.subviews.first as? UIImageView {
            imageView.image = imageData.img1.imageInBlackBox
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690