I calculate itemSize dependent on safe area for UICollectionView with horizontal scroll and custom layout.
But for iPhone X safe area has different size for different orientation. My question is how should I calculate safe area size for landscape orientation in viewWillTransition function? Or how is it possible to do without this calculation?
EDIT
To get safe area size without creating any additional views, use this:
view.safeAreaLayoutGuide.layoutFrame.size
If you want to use viewWillTransition method you can use this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// Before rotation
print(view.safeAreaLayoutGuide.layoutFrame.size)
coordinator.animate(alongsideTransition: { (context) in
// During rotation
}) { (context) in
// After rotation
print(self.view.safeAreaLayoutGuide.layoutFrame.size)
}
}
In the completion block you will get your desired size, note however, that this code will be called after the rotation.
Original answer
Solution using additional UIView:
What I did was to create a UIView and pin it with constant 0 to Safe Area Guides, so that it always matches size of Safe Area:
I created an #IBOutlet of that UIView and in viewDidLayoutSubviews() check the size:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(containerView.frame.size)
}
After rotation I also get the updated size of that UIView.
Use UICollectionViewDelegateFlowLayout's collectionView(_:layout:sizeForItemAt:). Works like a charm.
Setting the size in the completion block of coordinator.animate(alongsideTransition:completion:) in viewWillTransition(to:with:) didn't work for me because the size animation occurs after the orientation animation ends, which looks weird.
Related
My layout when on split screen is not respecting the width of the split screen.
My custom view is respecting it(the black bar at the top) but anything using autolayout is not respecting the width.
I am handling rotation using
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
Is there a helper method for handling split view? Do I handle it in layoutSubview? I would have expected the UICollectionView to handle this for us.
In viewWillTransition I use
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
flowLayout.invalidateLayout()
I assume by "default" layout you mean a UICollectionViewFlowLayout. It looks like the itemSize width is bigger then the width of the view. Try adding a check in collectionView:layout:sizeForItemAtIndexPath: to make sure that the width is less than or equal to the collection's width.
Yes. That issue still exist in iOS 13 beta 7.
If you implement multiple-window and split two collection view side by side. The only left window will layout correct (rotate several times could see that).
Add that in view controller can fix it:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionViewLayout.invalidateLayout()
}
When testing my app on an iPad using multitasking and I change the app size on the app, the collection view cells need to be resized or else they don't fit in the view window.
I have took some measures such as
detecting device to change the size of the cells
measure view size to make sure cells fit within the window
Add an observer if user changes device orientation
This is all great but however when actually changing the window size when using multitasking, or going back to the full screen app, the cell sizes don't change and look out of place. This is until forced by switching orientation or switching view controllers so viewWillLoad or viewDidLoad gets executed again.
So how can I set a notification if the view.frame size changes ?
I think the right function to override is viewWillTransition(to:, with:). It is called whenever the container size is about to change.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
self.collectionView?.collectionViewLayout.invalidateLayout()
}
I think you are over complicating the issue, just use the following so when the device is rotated or resized the collection view also changes accordingly.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard let collectionLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
if UIApplication.shared.statusBarOrientation == UIInterfaceOrientation.landscapeLeft ||
UIApplication.shared.statusBarOrientation == UIInterfaceOrientation.landscapeRight {
//here you can do the logic for the cell size if phone is in landscape
} else {
//logic if not landscape
}
collectionLayout.invalidateLayout()
}
I tried RileyDev answer but the app entered in an infinite loop.
There is no delegate that is executed just when the App's window bounds change, so I've made my own version of the viewWillTransition(to size and traitCollectionDidChange.
With the code below you can detect the window bounds resizing change even if the traits don't change, and resize your collection view cells accordingly invalidating the collection view layout.
var previousWindowBounds: CGRect?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let currentBounds = view.window?.bounds,
let previousWindowBounds = previousWindowBounds, currentBounds != previousWindowBounds {
collectionView?.collectionViewLayout.invalidateLayout()
}
previousWindowBounds = view.window?.bounds
}
I have a View Controller with a view hierarchy within two nested views that are placed with autolayout constraints.
I then place in code some UIViews into one of those views but I don't want to use constraints for these as the user can drag them around.
If the device gets rotated, or the user splits the view using the slide over functionality of the iPad I want to reset the locations of these UIViews into their new place based on the automatic layout of the parent views:
I have a function in the questionView called resetAnswerPosition() that correctly positions all of the Views based on the position of the reference View after the auto-layout positioning has kicked in. QuestionView has stores a reference to the ReferenceView, to make this easier.
The documentation suggests using viewWillTransitionToSize & willTransitionToTraitCollection to run these changes which works for rotation, or for when the slide over changes the aspect ratio from landscape to portrait or back again. However on an iPad Pro when change the split from 2/3 to 1/2 the aspect ratio isn't changing that the resetAnswerTilesPositions is being called before the Reference View has been resized.
I have also tried to use the viewDidLayoutSubViews but again have the issue that this is run before the Reference view has been resized.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (context) -> Void in
self.questionView.resetAnswerTilePositions()
},
completion: { (context) -> Void in
})
}
override func willTransitionToTraitCollection(newCollection: UITraitCollection, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
self.questionView.resetAnswerTilePositions()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.questionAnswerView.resetAnswerTilePositions()
}
What function can I register either with the View Controller or the QuestionView which will only be called once all of the views have been laid out according to the updated constraints?
As always posting a question led me to the right answer.
In the Question View you can override the layoutSubviews routine which is really nice from a separation of concerns of point of view. Only the Question View then needs to know how to layout its own subviews.
override func layoutSubviews() {
super.layoutSubviews()
self.resetAnswerTilePositions()
}
There is a view, which I add constraints in storyboard so it will change its size after screen rotation, then how can I get its height and width after screen rotation? I tried this in a rotation event function like this:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
println("h:\(view.bounds.size.height)")
println("w:\(view.frame.size.width)")
}
but it only give me the size before rotation happen. I would like to get something like:
"now is landscape, height is...width is..."
"now is portrait, height is ...width is..."
in a rotation event function
The function you are using says "Will transition", that means the transition is not completed, so you can not fetch the new size.
You need to use :
dispatch_async(dispatch_get_main_queue()) {
println("h:\(view.bounds.size.height)")
println("w:\(view.frame.size.width)")
}
This code will be executed after the rotation was completed (in the main queue) and the new sizes are available.
Regards.
Swift 3
crom87 answer updated for Swift 3:
override func viewWillTransition( to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator ) {
DispatchQueue.main.async() {
print( "h:\(self.view.bounds.size.height)" )
print( "w:\(self.view.frame.size.width)" )
}
}
I am looking for a way to enforce a UIPopoverPresentationController automatically resize to fill most of the screen, minus say 50 units of padding.
I first tried settings the preferredContentSize in viewDidLoad, and then detect when rotation occurs and update that CGSize to the new size I could calculate. But for some reason, this isn't working in my context. I was using UIDeviceOrientationDidChangeNotification which normally calls the method, but it's not called when the popover exists within a photo editing extension.
I am wondering if there's any other way to change the popover size besides manually updating the preferredContentSize after detecting a size change? If not, is there a way to detect rotation from within this popover in this context?
Try this:
func isLandscape() -> Bool {
// your logical here
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let myNewSize = isLandscape() ? mySizeLandscape : mySizePortrait
self.preferredContentSize = myNewSize
super.viewWillTransition(to: myNewSize, with: coordinator)
}