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?
//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>) {
}
}
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.
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
}
}
}
Related
I am using SwiftUI with RealityKit. As displayed in the code below, I have a plane entity that when tapped simply prints the name of the entity. What approach should I take toward navigating to a new view when I tap the entity? It would be preferable to navigate as with a navigation link in a normal view, but if that is not possible then perhaps a fullScreenCover?
ARViewContainer.swift:
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
print(entity.name)
}
}
}
struct ARViewContainer: UIViewRepresentable {
typealias UIViewType = ARView
func makeUIView(context: Context) -> ARView{
let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)
context.coordinator.view = arView
arView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
arView.scene.anchors.removeAll()
let anchor = AnchorEntity()
let plane = MeshResource.generatePlane(width: 1, height: 1)
var material = UnlitMaterial()
material.color = .init(tint: .white,
texture: .init(try! .load(named: "instagram")))
let planeEntity = ModelEntity(mesh: plane, materials: [material])
planeEntity.generateCollisionShapes(recursive: true)
planeEntity.name = "Plane Entity"
planeEntity.position.z -= 1.0
planeEntity.setParent(anchor)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context){
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
ContentView.swift
struct ContentView: View {
#State var open = false
var body: some View {
NavigationView{
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
}
}
}
}
View I want to navigate to:
struct TestView : View {
var body : some View {
VStack{
Text("Test View")
}
}
}
Manage the state of the view in an observable object and modify it from your AR view.
struct ContentView: View {
#ObservedObject var settings = Settings.shared
var body: some View {
NavigationView {
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
NavigationLink("", isActive: $settings.shouldOpenDetailsView) {
TestView()
}
}
}
}
}
class Settings: ObservableObject {
static let shared = Settings()
#Published var shouldOpenDetailsView = false
}
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
Settings.shared.shouldOpenDetailsView = true
}
}
}
I have an image picker created with PHPicker, and I was wondering if it is possible to let the user scale the chosen image?
This is not the entire code, but just the code for the makeUIViewController which I think is what is needed to solve this problem. I can of course provide the rest of the code if necessary.
This is what I'm looking for
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images
config.selectionLimit = 1
let controller = PHPickerViewController(configuration: config)
controller.delegate = context.coordinator
return controller
}
can use this one line after choose the image to fixed height and width of your image
Image(room.thumbnailImage)
.resizable()
.frame(width: 32.0, height: 32.0)
or here i am sharing my running work with you checkout function didFinishPicking and var body: some View
import SwiftUI
import PhotosUI
struct PhotoPickerDemo: View {
#State private var isPresented: Bool = false
#State var pickerResult: [UIImage] = []
var config: PHPickerConfiguration {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images //videos, livePhotos...
config.selectionLimit = 0 //0 => any, set 1-2-3 for har limit
return config
}
var body: some View {
ScrollView {
LazyVStack {
Button("Present Picker") {
isPresented.toggle()
}.sheet(isPresented: $isPresented) {
PhotoPicker(configuration: self.config,
pickerResult: $pickerResult,
isPresented: $isPresented)
}
ForEach(pickerResult, id: \.self) { image in
Image.init(uiImage: image)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 250, alignment: .center)
.aspectRatio(contentMode: .fit)
}
}
}
}
}
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.parent.pickerResult.append(newImage as! UIImage)
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}
struct photoPickerDemo_Previews: PreviewProvider {
static var previews: some View {
PhotoPickerDemo()
}
}
or if you want to crop via user interface like attach picture
Step 1
Using Xcode 12, go to File -> Swift Packages -> Add Package Dependency and enter https://github.com/marshallino16/ImageCropper
Step 2
in your didFinishPicking method where you are receiving selected image pass it in this package using these lines
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(yourSelectedImageHere),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
edited use of ImageCropperView
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(newImage),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}
I am trying to get the user to pick an image from their gallery. After doing some research I was able to accomplish this task by using a UIViewControllerRepresentable. However, after I opened up the memory debugger it shows that I have a memory leak
I created a basic example to which would make the code a lot simpler to read and still address the same issue without copy-pasting my entire code. the code below also shows a memory leak
// code for the views
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: ImagePickerView(),
label: {
Text("Navigate")
})
}
}
}
struct ImagePickerView: View {
#State var image = UIImage()
#State var showController: Bool = false
#State var didChoose : Bool = false
var body: some View {
Button(action: {
showController = true
}, label: {
Text("pick image")
})
.sheet(isPresented: $showController, content: {
ImagePicker(image: $image, didChoose: $didChoose)
})
}
}
struct ImagePicker : UIViewControllerRepresentable {
#Binding var image: UIImage
#Binding var didChoose: Bool
#Environment(\.presentationMode) var presentation
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIViewController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: ImagePicker.UIViewControllerType, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent : ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let pickedImage = info[.originalImage] as? UIImage else {
print("could not unwrap the image")
return
}
print(pickedImage)
self.parent.image = pickedImage
self.parent.didChoose = true
self.parent.presentation.wrappedValue.dismiss()
}
}
}
I suspect the cause to be the in the coordinator class however, ifailed to identify the reason behind it. is it involved with the parent?
I am trying to observe the progress when uploading an image to firebase storage. The progress is being updated in the console however my ProgressView does not update. Everything else seems to be working as expected. Any idea what I'm doing wrong?
class UserManager: ObservableObject {
#Published var taskProgress: Float = 0.0
let user = Auth.auth().currentUser
func uploadProfilePicture(image: UIImage) {
let uploadRef = FirebaseReferenceManager.storage.reference(withPath: "profile/\(user!.uid)")
guard let imageData = image.jpegData(compressionQuality: 0.75) else {
return
}
let uploadMetaData = StorageMetadata.init()
uploadMetaData.contentType = "image/jpeg"
let taskReference = uploadRef.putData(imageData, metadata: uploadMetaData) { (downloadMetaData, error) in
if let error = error {
print(error.localizedDescription)
return
} else {
print("Successfully Uploaded Profile Picture to Firebase Storage")
let downloadRef = FirebaseReferenceManager.storage.reference(withPath: "profile/\(self.user!.uid)")
downloadRef.downloadURL { (url, error) in
if let error = error {
print(error.localizedDescription)
return
}
FirebaseReferenceManager.root.collection(FirebaseKeys.CollectionPath.users).document(self.user!.uid).setData([FirebaseKeys.UsersFieldPath.photoURL : url!.absoluteString], merge: true)
}
}
}
taskReference.observe(.progress) { [weak self] (snapshot) in
DispatchQueue.main.async {
guard let pctThere = snapshot.progress?.fractionCompleted else {return}
print(pctThere)
self?.taskProgress = Float(pctThere)
}
}
taskReference.resume()
}
}
In my view I put the following
#EnvironmentObject var userManager: UserManager
ProgressView(value: userManager.taskProgress).progressViewStyle(LinearProgressViewStyle())
Here is how the class is initialized:
struct SwiftUIView: View {
#StateObject var userManager = UserManager()
var body: some View {
TabView {
HomeView().tabItem {
Image("home")
Text("Home")
}
SearchView().tabItem {
Image("search")
Text("Search")
}
DiscoverView().tabItem {
Image("discover")
Text("Discover")
}
OrdersView().tabItem {
Image("calendar")
Text("Orders")
}
InboxView().tabItem {
Image("inbox")
Text("Inbox")
}
}
.environmentObject(userManager)
}
}
And then I pass it down deeper into the hierarchy using
#EnvironmentObject var userManager: UserManager
Here is where uploadProfilePicture() is called:
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var selectedImage: UIImage
#Environment(\.presentationMode) private var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.delegate = context.coordinator
imagePicker.allowsEditing = true
imagePicker.sourceType = sourceType
return imagePicker
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
#ObservedObject private var userManager = UserManager()
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage {
parent.selectedImage = image
userManager.uploadProfilePicture(image: image)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
From the comments, we deduced that it was a different UserManager instance. Here would be an example of how to pass the same instance into the Coordinator (this is assuming that ImagePicker exists in an environment where #EnvironmentObject var userManager: UserManager is available).
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var selectedImage: UIImage
#Environment(\.presentationMode) private var presentationMode
#EnvironmentObject private var userManager: UserManager //<-- Here
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.delegate = context.coordinator
imagePicker.allowsEditing = true
imagePicker.sourceType = sourceType
return imagePicker
}
func makeCoordinator() -> Coordinator {
Coordinator(self, userManager: userManager) //<-- Here
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
let userManager: UserManager
init(_ parent: ImagePicker, userManager: UserManager) { //<-- Here
self.parent = parent
self.userManager = userManager //<-- Here
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage {
parent.selectedImage = image
userManager.uploadProfilePicture(image: image)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
I have a working implementation of SwiftUI + Camera using UIImagePickerController, but I am having trouble reloading the camera again after I take a photo (so i can take multiple pictures)
What happens is that after I take a picture, I get the "Retake/Use Photo". I select "Use Photo", and the delegate methods get called and I can grab the photo, great. After that however, the View remains the same, still showing a still image with the "Retake/Use Photo" buttons
How can I trigger an update so I get the Camera again, with the desire being: Show Camera, Hit Button, select "Use Photo", then the camera shows again
// SampleQuestion.swift
import SwiftUI
struct SampleQuestion: View {
#State var photos: [UIImage] = []
var body: some View {
return VStack{
CameraViewIntegratedController(userPhotos: $photos)
.frame(width: 400, height: 400)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
// CameraViewIntegratedController
import SwiftUI
import UIKit
struct CameraViewIntegratedController: UIViewControllerRepresentable {
#Binding var userPhotos: [UIImage]
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CameraViewIntegratedController>) -> UIImagePickerController {
let vc = UIImagePickerController()
vc.sourceType = .camera
vc.allowsEditing = true
vc.delegate = context.coordinator
return vc
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var parent: CameraViewIntegratedController
init(_ imagePickerController: CameraViewIntegratedController) {
self.parent = imagePickerController
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info:[UIImagePickerController.InfoKey : Any]) {
guard let image = info[.editedImage] as? UIImage else {
print("No image found")
return
}
print("Image Taken Successfully, with size \(image.size)")
parent.userPhotos.append(image)
}
}
}
Ideally with after didFinishPickingMediaWithInfo I can reinitialize the camera. Or perhaps its on the SwiftUI side working with #State ?