5
votes

I am using UIImagePickerController to capture a photo from the camera or select from the photo library, and then display the image. This works fine for landscape/horizontal oriented photos, but photos in portrait/vertical orientation are showing up stretched out horizontally.

I've tried .scaledToFit() and .aspectRatio(contentMode: .fit), but it still shows up stretched. Any help is very much appreciated.

See stretched photo

Code to display image:

struct AddNewItem: View     {

@State private var showImagePicker: Bool = true
@State private var image: UIImage = nil
@State var showCamera: Bool = false
@State private var showImageEditor: Bool = false


var body: some View {
    VStack {

        Image(uiImage: image ?? UIImage(named: "placeholder")!)
            .resizable()
            .scaledToFit()
            .aspectRatio(contentMode: .fit)

    }
    //Show the photo capture view
    .sheet(isPresented: self.$showImagePicker) {
        PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image, showImageEditor: self.$showImageEditor, showCamera: self.$showCamera)
    }
}

}

Image Picker Coordinator:

class ImagePickerCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

@Binding var isShown: Bool
@Binding var image: UIImage?
@Binding var showEditor: Bool

init(isShown: Binding<Bool>, image: Binding<UIImage?>, showEditor: Binding<Bool>) {
    _isShown = isShown
    _image = image
    _showEditor = showEditor
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
    image = uiImage
    isShown = false
    showEditor = true
}

func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    isShown = false
}

}

Image Picker:

struct ImagePicker: UIViewControllerRepresentable {

@Binding var pickerIsShown: Bool
@Binding var image: UIImage?
@Binding var showImageEditor: Bool
@Binding var showCamera: Bool

func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {

}

func makeCoordinator() -> ImagePickerCoordinator {
    return ImagePickerCoordinator(isShown: $pickerIsShown, image: $image, showEditor: $showImageEditor)
}

func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {

    let picker = UIImagePickerController()

    if showCamera {
        picker.sourceType = .camera
    } else {
        picker.sourceType = .photoLibrary
    }

    picker.delegate = context.coordinator

    return picker
}

}

Photo Capture View:

struct PhotoCaptureView: View {

@Binding var showImagePicker: Bool
@Binding var image: UIImage?
@Binding var showImageEditor: Bool
@Binding var showCamera: Bool

var body: some View {
    ImagePicker(pickerIsShown: $showImagePicker, image: $image, showImageEditor: $showImageEditor, showCamera: $showCamera)
}

}

4

4 Answers

2
votes

I'm having the same issue. I managed to avoid it by turning editingMode to true inside the makeUIViewController function:

func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
    let imagePicker = UIImagePickerController()
    imagePicker.allowsEditing = true
    imagePicker.delegate = context.coordinator
    return imagePicker
}

And by using the edited image instead of the original one inside the didFinishPickingMedia with info function:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let image = info[.editedImage] as? UIImage else {
            print("No image found")
            return
        }
        parent.imageController.originalImage = image
        parent.showImagePicker = false
    }

This prevents the image from being stretched. However the user is only able to choose a photo that gets cropped a as square. Im working on a workaround..

1
votes

I ended up working around by getting the dimensions of each image to calculate the aspect ratio, then applying the aspect ratio manually with .aspectRatio(self.imageAspectRatio, contentMode: .fit)

   func calculateAspectRatio(image: UIImage) -> CGFloat {
    let imageW = image.size.width
    let imageH = image.size.height
    let imageAspectRatio = imageW/imageH
    return imageAspectRatio
} 



Image(uiImage: image ?? UIImage(named: "placeholder")!)
     .aspectRatio(self.imageAspectRatio, contentMode: .fit)
0
votes

I found something. It looks like work here. Well in didFinishPickingMediaWithInfo you should rotate the selected image with 360 degree.

        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            parent.selectedImage = image.rotate(radians: (360.0 * .pi / 180))!
            parent.selectedVideoURL = URL(fileURLWithPath: "")
        }

I've get the rotate method from this post: Rotating UIImage in Swift

I hope you works.

-1
votes

Solved the issue by redrawing the image in a Graphics Context before displaying:

func redraw(image: UIImage) -> UIImage? {
  let renderer = UIGraphicsImageRenderer(size: image.size)
  return renderer.image { (context) in
      image.draw(in: CGRect(origin: .zero, size: image.size))
  }
}

With the wrapped UIImagePickerController:

struct ImagePickerController: UIViewControllerRepresentable {
  @Binding var image: UIImage?

  func makeCoordinator() -> ImagePickerController.Coordinator {
    Coordinator(self)
  }

  func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerController>) -> UIImagePickerController {
    let imagePicker = UIImagePickerController()
    imagePicker.delegate = context.coordinator
    imagePicker.sourceType = .camera

    return imagePicker
  }

  func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerController>) {

  }

  class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    var controller: ImagePickerController

    init(_ controller: ImagePickerController) {
      self.controller = controller
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
      picker.dismiss(animated: true, completion: nil)

      guard let image = info[.originalImage] as? UIImage else {
        return
      }

      // Need to draw in Graphics Context to fix SwiftUI aspect ratio issue
      let redrawnImage = redraw(image: image)!

      controller.image = redrawnImage
    }
  }
}

Presented in a SwiftUI view as a sheet:

struct CameraPicker: View {
  @State var isOpen = false
  @State var image: UIImage?

  var body: some View {
    VStack {
      if image != nil {
        Image(uiImage: image!)
          .resizable()
          .scaledToFit()
          .frame(width: 60, height: 60, alignment: .center)
      }

      Button(action: {
        self.isOpen = true
      }) { Text("Take Photo") }
    }
  }.sheet(isPresented: $isOpen, onDismiss: {
    self.isOpen = false
  }, content: {
    ImagePickerController(image: self.$image)
  })
}