Is there a way in iOS 16's expanded UISheetPresentationController API to set a minimum detent height for a presented sheet? (i.e. such that it does not dismiss from the screen?)
Example presentation code:
let viewControllerToPresent = UIViewController()
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(viewControllerToPresent, animated: true, completion: nil)
Adding viewControllerToPresent.isModalInPresentation = true as a property enables this experience.
Related
I have created s custom UI and assigned it to the camera overlay of UIImagePickerController as below.
Custom View
cameraOverlay = RecordView.loadNib()
cameraOverlay.frame = UIScreen.main.bounds
cameraOverlay.buttonStartRecording.addTarget(self, action: #selector(buttonStartStopRecordingClicked),
for: .touchUpInside)
cameraOverlay.buttonCancel.addTarget(self, action: #selector(buttonCancelClicked), for: .touchUpInside)
cameraOverlay.buttonSwitchCamera.addTarget(self, action: #selector(buttonCameraSwitchClicked), for: .touchUpInside)
cameraOverlay.buttonFlash.addTarget(self, action: #selector(buttonFlashClicked), for: .touchUpInside)
cameraOverlay.labelTimer.text = "00:00/\(self.secondsToHoursMinutesSeconds(inputSeconds: recordingTimeLimit))"
Image Picker Controller
imagePicker.delegate = self
imagePicker.sourceType = .camera
imagePicker.mediaTypes = [kUTTypeMovie as String]
imagePicker.allowsEditing = true
imagePicker.cameraOverlayView = cameraOverlay
imagePicker.showsCameraControls = false
imagePicker.cameraFlashMode = .off
imagePicker.cameraCaptureMode = .video
imagePicker.cameraDevice = .rear
self.present(imagePicker, animated: true, completion: nil)
This works fine with the camera without a notch but UI gets cut from the top behind the notch on iPhone X and above. I somehow need to set my custom overlay within the safe area but not sure how that can be done. Please can someone help me out here. Thanks in advance.
I checked whether the phone has a notch
var hasNotch: Bool {
let bottom = UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0
return bottom > 0
}
And then handle the Constraints based on that
topHeightConstraint.constant = hasNotch ? 94 : 50
instructionBottomConstriant.constant = hasNotch ? 60 : 40
I'm using a custom UIPresentationController to present a view modally. After presenting the view, the first textfield in the presented view becomes the first responder and the keyboard shows up. To ensure that the view is still visible, I move it up. However, when I do this the frameOfPresentedViewInContainerView is not matching the actual frame of the view anymore. Because of this, when I tap on the view it's being dismissed, because there's a tapGestureRecogziner on the backgroundView which is on top of the presentingView. How to notify the presentingController that the frame/position of the presentedView has changed?
In the UIPresentationController:
override var frameOfPresentedViewInContainerView: CGRect {
var frame = CGRect.zero
let safeAreaBottom = self.presentingViewController.view.safeAreaInsets.bottom
guard let height = presentedView?.frame.height else { return frame }
if let containerBounds = containerView?.bounds {
frame = CGRect(x: 0,
y: containerBounds.height - height - safeAreaBottom,
width: containerBounds.width,
height: height + safeAreaBottom)
}
return frame
}
override func presentationTransitionWillBegin() {
if let containerView = self.containerView, let coordinator = presentingViewController.transitionCoordinator {
containerView.addSubview(self.dimmedBackgroundView)
self.dimmedBackgroundView.backgroundColor = .black
self.dimmedBackgroundView.frame = containerView.bounds
self.dimmedBackgroundView.alpha = 0
coordinator.animate(alongsideTransition: { _ in
self.dimmedBackgroundView.alpha = 0.5
}, completion: nil)
}
}
Presenting the view modally:
let overlayVC = CreateEventViewController()
overlayVC.transitioningDelegate = self.transitioningDelegate
overlayVC.modalPresentationStyle = .custom
self.present(overlayVC, animated: true, completion: nil)
Animation when keyboard appears (in the presented view):
#objc func animateWithKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
guard let keyboardHeight = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height,
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else {
return
}
// bottomContraint is the constraint that pins content to the bottom of the superview.
let moveUp = (notification.name == UIResponder.keyboardWillShowNotification)
bottomConstraint.constant = moveUp ? (keyboardHeight) : originalBottomValue
let options = UIView.AnimationOptions(rawValue: curve << 16)
UIView.animate(withDuration: duration, delay: 0,
options: options,
animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
From the Apple documentation:
UIKit calls this method multiple times during the course of a
presentation, so your implementation should return the same frame
rectangle each time. Do not use this method to make changes to your
view hierarchy or perform other one-time tasks.
AFAIK, if you specify frame through this variable, it's advised not to change it throughout the course of presentation. If you plan to play around with the frames, don't specify this variable and handle all the changes manually in your animator
When loading the PDF in a QLPreviewController, it does not fit the content that I have inside, I am adding it as a subview to a container view. The PDF has a greater zoom than required. Is there any way to modify the zoom of QLPreview?
let preview = QLPreviewController()
preview.dataSource = self
preview.navigationController?.isNavigationBarHidden = true
addChild(preview)
termsConditionsContainer.addSubview(preview.view)
termsConditionsContainer.backgroundColor = .white
preview.view.translatesAutoresizingMaskIntoConstraints = false
preview.view.topAnchor.constraint(equalTo: termsConditionsContainer.topAnchor).isActive = true
preview.view.leftAnchor.constraint(equalTo: self.termsConditionsContainer.leftAnchor).isActive = true
preview.view.widthAnchor.constraint(equalTo: self.termsConditionsContainer.widthAnchor).isActive = true
preview.view.bottomAnchor.constraint(equalTo: termsConditionsContainer.bottomAnchor).isActive = true
preview.didMove(toParent: self)
I am including a search bar by code and enabling interaction using the code below, the search bar is displayed but when I click on the field editing is not enabled:
let searchTableViewController = storyboard?.instantiateViewController(withIdentifier: "SearchBarTableVC") as? SearchBarTableVC
searchTableViewController?.handleMapSearchDelegate = self
searchTableViewController?.mapView = self.mapView
searchBarController = UISearchController(searchResultsController: searchTableVC)
searchBarController?.searchResultsUpdater = searchTableVC
let searchBar = searchBarController?.searchBar
searchBar?.sizeToFit()
searchBar?.placeholder = "Digite o local"
searchBar?.tintColor = UIColor.red
searchBar!.isUserInteractionEnabled = true
searchBar?.backgroundColor = UIColor(named: "ColorTransparent")
navigationItem.titleView = searchBarController?.searchBar
searchBarController?.hidesNavigationBarDuringPresentation = false
searchBarController?.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
If you're using iOS 11, the preferred way is simply to write:
navigationItem.searchController = searchController
That's all. You don't have to position the search bar or put it in any view.
In my app, I want the user to be able to be able to take a picture, be presented with the picture, and by tapping the photo a textfield can be added so that they can write on top of the image. This is exactly the same as the functionality of adding text to pictures in Snapchat.
As far as I can understand, the only way to be presented the image after having taken it and be able to edit it, is to set:
imagePicker.showsCameraControls = false
Make a custom overlay:
#IBAction func takePhoto(sender: UIButton) {
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .Camera
imagePicker.showsCameraControls = false
imagePicker.allowsEditing = true
let overlayView = UIView(frame: CGRectMake(0, self.view.frame.width, self.view.frame.width, self.view.frame.height-self.view.frame.width))
overlayView.backgroundColor = UIColor.blackColor()
overlayView.alpha = 0.5
println(overlayView)
let snapButton = UIView(frame: CGRectMake(150, 160, 80, 80))
snapButton.layer.cornerRadius = 40
snapButton.userInteractionEnabled = true
snapButton.backgroundColor = UIColor.purpleColor()
overlayView.addSubview(snapButton)
overlayView.bringSubviewToFront(snapButton)
let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleSnapTap:"))
recognizer.delegate = self
snapButton.addGestureRecognizer(recognizer)
let cancelButton = UIView(frame: CGRectMake(40, 40, 44, 44))
cancelButton.layer.cornerRadius = 22
cancelButton.userInteractionEnabled = true
cancelButton.backgroundColor = UIColor.redColor()
overlayView.addSubview(cancelButton)
overlayView.bringSubviewToFront(cancelButton)
let cancelRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleCancelTap:"))
cancelRecognizer.delegate = self
cancelButton.addGestureRecognizer(cancelRecognizer)
let changeCameraButton = UIView(frame: CGRectMake(165, 40, 44, 44))
changeCameraButton.layer.cornerRadius = 22
changeCameraButton.userInteractionEnabled = true
changeCameraButton.backgroundColor = UIColor.blueColor()
overlayView.addSubview(changeCameraButton)
overlayView.bringSubviewToFront(changeCameraButton)
let changeCameraRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleChangeCameraTap:"))
changeCameraRecognizer.delegate = self
changeCameraButton.addGestureRecognizer(changeCameraRecognizer)
let flashButton = UIView(frame: CGRectMake(300, 40, 44, 44))
flashButton.layer.cornerRadius = 22
flashButton.userInteractionEnabled = true
flashButton.backgroundColor = UIColor.yellowColor()
overlayView.addSubview(flashButton)
overlayView.bringSubviewToFront(flashButton)
let flashRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleFlashTap:"))
flashRecognizer.delegate = self
flashButton.addGestureRecognizer(flashRecognizer)
imagePicker.cameraOverlayView = overlayView
presentViewController(imagePicker, animated: true, completion: nil)
}
func handleSnapTap(recognizer: UITapGestureRecognizer) {
println("Take picture")
imagePicker.takePicture()
self.performSegueWithIdentifier("cameraToImageViewSegue", sender: self)
}
func handleCancelTap(recognizer: UITapGestureRecognizer) {
println("Cancel")
self.imagePicker.dismissViewControllerAnimated(true, completion: nil)
}
func handleChangeCameraTap(recognizer: UITapGestureRecognizer) {
if (hasChangedCamera == nil){
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.Front
hasChangedCamera = true
return
}
if (hasChangedCamera == true){
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.Rear
hasChangedCamera = false
return
}
if (hasChangedCamera! == false){
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.Front
hasChangedCamera = true
return
}
}
func handleFlashTap(recognizer: UITapGestureRecognizer) {
if (hasTurnedOnFlash == nil){
imagePicker.cameraFlashMode = UIImagePickerControllerCameraFlashMode.On
hasTurnedOnFlash = true
return
}
if (hasTurnedOnFlash == true){
imagePicker.cameraFlashMode = UIImagePickerControllerCameraFlashMode.Off
hasTurnedOnFlash = false
return
}
if (hasTurnedOnFlash == false){
imagePicker.cameraFlashMode = UIImagePickerControllerCameraFlashMode.On
hasTurnedOnFlash = true
return
}
}
And finally present a new view controller in which the picked image is placed in a UIView, and edit it from there. My issue is how to segue directly from the UIImagePickerController to a new view controller. I have tried the following:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
self.imagePicker.dismissViewControllerAnimated(false, completion: nil)
let vc = ModifyCameraImageViewController() //change this to your class name
self.presentViewController(vc, animated: false, completion: nil)
}
First off, this just leads to a black screen, but I'm sure there's a simple enough way around that. My main issue is the fact that the view controller from which the UIImagePickerController was presented briefly appears on the screen before the next view controller appears. This obviously does not look good. I also tried removing the dismissViewController function, as well as placing the presentViewController function above the dismissView controller function. Both of these attempts gave me the error message:
Warning: Attempt to present <xxx.ModifyCameraImageViewController: 0x145e3eb70> on <xxx.ViewController: 0x145d20a60> whose view is not in the window hierarchy!
Attempting to use performSegueWithIdentifier with a segue linking the underlying view and the next view controller gives the same error warning.
I have found the following similar question, but I am completely inept at Objective C, so I'm struggling to make any sense of it: Push a viewController from the UIImagePickerController camera view
So, can anyone help in regards to how to present a view controller directly from the UIImagePickerController?
Also, keep in mind that I'm doing this in order to be able to create a text overlay on the newly picked image (like in Snapchat), so if anyone has a more elegant solution to that, feel free to post it!
Thanks!
Ok, found a simple solution to my issue. Instead of presenting the imagePickerController from the underlying view controller when the takePicture button is pressed, and segueing to another view controller directly from there, use the takePicture button to segue to another view controller and present the imagePickerController from the viewDidLoad of the second view controller. The second view controller will then be presented when the imagePickerController is dismissed. This however requires the underlying view controller to look similar to the camera controls, and some playing around with animations for this to look natural.
let pickerController: Void = picker.dismissViewControllerAnimated(true) { _ in
UIImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
self.performSegueWithIdentifier("segueIdentifier", sender: nil)
//Do what you want when picker is dismissed
}