in iOS9 when I rotate my screen with a UIPopoverPresentationController the coordinates are reset to 0 and not attached to my sourceView which is a button.
I have tried:
func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer, inView view: AutoreleasingUnsafeMutablePointer) {
rect.initialize(CGRectMake(200, 200, 400, 400))
}
but no avail. Any help ?
You could apply constraints to you sourceView button, then just use it as the popover source:
myPopoverViewController.popoverPresentationController?.sourceView = button
You also can set sourceView in the storyboard, but sourceRect needs to be set in code:
myPopoverViewController.popoverPresentationController?.sourceRect = button.bounds
You need the correct source rect for the popover arrow to align properly.
Now you don't need to dismiss and present the popover on rotation. UIPopoverPresentationController does that for you. You don't even need to update sourceView/sourceRect once they are set on creating the popover.
Use viewWillTransition to catch size and orientation changes:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
if self.popover != nil {
// optionally scroll to popover source rect, if inside scroll view
let rect = ...
self.scrollView.scrollRectToVisible(rect, animated: false)
// update source rect constraints
buttonConstraint.constant = ...
button.setNeedsLayout()
button.layoutIfNeeded()
}
}, completion: nil)
}
Explanation:
The trick with animate(alongsideTransition: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) is that you should update your constraints in alongsideTransition closure, not in completion. This way you ensure that UIPopoverPresentationController has the updated sourceRect when restoring the popover at the end of rotation.
What might seem counter-intuitive is that inside alongsideTransition closure you already have your new layout that you derive your constraints calculation from.
Related
I've implemented the iOS 11 feature prefersLargeTitles and it works just fine. Portrait mode is working as expected:
I understand the large title will always stay collapsed (small) in landscape mode and that's fine to me. The problem is when I try to change to landscape and then again to portrait, the large title should be expanded (big) by default back in portrait mode, but it won't until I scroll down a bit:
My code looks quite simple:
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
}
I also tried using different values on tableView.contentInsetAdjustmentBehavior, nothing changed. I'm kind of solving it by now scrolling down the table programmatically after orientation changes, but I think that's just a (not very nice) workaround.
Is that supposed to be working as expected? Is it something left in my implementation? Is there a better workaround to this?
I faced the same issue. This worked for me.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
navigationItem.largeTitleDisplayMode = .always
coordinator.animate(alongsideTransition: { (_) in
self.coordinator?.navigationController.navigationBar.sizeToFit()
}, completion: nil)
}
One approach could be save the maximum navigation bar height, and set it during rotation.
Something like this:
var maximumHeight: CGFloat = 0
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard let navigationController = navigationController else {
return
}
if maximumHeight < navigationController.navigationBar.frame.height {
maximumHeight = navigationController.navigationBar.frame.height
}
coordinator.animate(alongsideTransition: { (_) in
navigationController.navigationBar.frame.size.height = self.maximumHeight
}, completion: nil)
}
In landscape, the system knows that it must change its size, so you don't have to worry about it.
#rassar #twofish
iOS 16
First set the PrefersLargeTitles to true either on ViewDidLoad() or on the NavigationBar in the storyboard.
Then on the Navigation Controller's rootController add this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { (_) in
self.navigationController?.navigationBar.sizeToFit()
}
}
I created a blurred view on top of a scroll view (from a collection view) who is displaying on all the screen when user go to the settings but when orientation change when this blurview is active, it doesn't cover all the screen. I implemented the same function to update the frame size and origin when orientation change is catched but it still doesn't work.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
self.detailBlurView.removeFromSuperview()
if UIDevice.current.orientation.isLandscape {
print("Landscape")
detailBlurView.frame = myCollectionView.bounds
self.myCollectionView.addSubview(detailBlurView)
} else {
print("Portrait")
detailBlurView.frame = myCollectionView.bounds
self.myCollectionView.addSubview(detailBlurView)
}
saveToCoreData()
myCollectionView.reloadData()
loadData()
}
I tried to remove the view from the superview and add it again after changing orientation but still not work.
Someone can tell me why it doesn't update the blurview frame and how to fix it please ?
I need to make below table like structure in one of the apps I am developing. I am creating this table using UITableView & I need my table column width to change based on the screen orientation. I have set constraints for column width & I will assign values to these using the screen width in my viewDidLoad().
However, I am not able to figure out how to re-align these constraints when screen orientation changes. I figured out that viewWillTransition() will be called when orientation is changed & I recalculated the constraints inside that & called setNeedsLayout() for the table view. However, I am not able to make my table view to reset the table column width when screen orientation is changed. I am new to IOS platform and any help will be greatly appreciated.
HDR_parName/HDR_parValue/HDR_minValue/HDR_maxValue
Para1/value1/minvalue1/maxvalue1
Para2/value2/minvalue2/maxvalue2
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
let animationHandler: ((UIViewControllerTransitionCoordinatorContext) -> Void) = { [weak self] (context) in
// This block will be called several times during rotation,
// so if you want your tableView change more smooth reload it here too.
self?.tableView.reloadData()
}
let completionHandler: ((UIViewControllerTransitionCoordinatorContext) -> Void) = { [weak self] (context) in
// This block will be called when rotation will be completed
self?.tableView.reloadData()
}
coordinator.animateAlongsideTransition(animationHandler, completion: completionHandler)
}
Then tableView datasource and delegate methods will be called, where you can setup elements size according to tableView frame size.
Swift 4
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let animationHandler: ((UIViewControllerTransitionCoordinatorContext) -> Void) = { [weak self] (context) in
// This block will be called several times during rotation,
// so if you want your tableView change more smooth reload it here too.
self?.yourTable.reloadData()
}
let completionHandler: ((UIViewControllerTransitionCoordinatorContext) -> Void) = { [weak self] (context) in
// This block will be called when rotation will be completed
self?.yourTable.reloadData()
}
coordinator.animate(alongsideTransition: animationHandler, completion: completionHandler)
}
It is quite simple solution, what you are doing is wrong.
Consider you want full width as your row width.
You don't have to set any constraint to cell width, instead set trailing constraint to your tableview. It will handle all.
If you still face similar issue then try
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
self.view.layoutIfNeeded()
}
let me know if you are still facing the issue.
I have a full-screen UIView. When I rotate the phone the view shrinks a little (and you see the black background) and then expands again to be full-screen. It basically animates as expected.
The other views on the screen also animate accordingly.
Can I prevent this from happening for one specific view?
I would like the full-screen view to just stay full-screen without animation revealing the black background, but maintain that the other views animate their rotation.
Kind of like how Apple does it in the camera app. The "viewfinder" does not animate its rotation, but the buttons do.
I have the following code in my ViewController. liveView is the the full-screen UIView mentioned.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animateAlongsideTransition(in: self.liveView, animation: { (context) in
if let connection = (self.liveView.layer as! AVCaptureVideoPreviewLayer).connection {
connection.videoOrientation = AVCaptureVideoOrientation(ui: UIApplication.shared.statusBarOrientation)
}
}, completion: nil)
}
AVCaptureVideoOrentation is an extension which basically translates UIInterfaceOrientation to AVCaptureVideoOrientation with a switch statement.
Thanks
- Joseph
Did you try self.liveView.layer.removeAllAnimations():
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animateAlongsideTransition(in: self.liveView, animation: { (context) in
if let connection = (self.liveView.layer as! AVCaptureVideoPreviewLayer).connection {
connection.videoOrientation = AVCaptureVideoOrientation(ui: UIApplication.shared.statusBarOrientation)
}
self.liveView.layer.removeAllAnimations()
}, completion: nil)
}
I'm working on this project and I have one scene on the Storyboard.
The scene is a TableViewController.
The table view have a custom prototype cell (linked to CustomCell.swift).
Inside the prototype cell there's a label and a custom UIView (linked to CustomView.swift). These elements have layout constraints relative to the contentView of the prototype cell.
Now, I want the stuff being drawn on my custom view to change when the size of the view changes, so that when the device rotates it is adjusted to that new cell width. Because of the constraints, the frame of CustomView will change when the CustomCell changes size, after the device is rotated. In order to detect this, I added two property observers to CustomView.swift:
override var frame: CGRect {
didSet {
print("Frame was set!")
updateDrawing()
}
}
override var bounds: CGRect {
didSet {
print("Bounds were set!")
updateDrawing()
}
}
When running the project, the second observer works fine when I rotate the device. The first observer does not. My question is why does the first observer not detect that the frame has changed?
.frame is computed from the .bounds and the .center of the view (and the transform), so it does not change. In order to react to rotation override this (starting from iOS8):
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (coordinator) -> Void in
// do your stuff here
// here the frame has the new size
}, completion: nil)
}