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)
}
Related
Currently I am working on a task where I am suppose expand UICollectionView cells based on the label inside. I am written constraints programmatically for this. The resizing occurs during this call.
override func preferredLayoutAttributedFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.width = ceil(size.width)
layoutAttributes.frame = frame
return layoutAttributes
}
This code works fine as expected, however, when I try to rotate, the cells which didn't appeared before rotation, the cells doesn't expand for them.
Is there any call I can make at cellForRowAtIndex or else place to force this preferredLayout to be called every time?
Or is there any easier process than this to avoid this calculation and directly update the cell size? I have updated estimateSize in layout, but I still need to call this method.
use viewWillTransition method
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
self.collectionView.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've built detail view in Interface Builder showing informations and photos about some object. Because lenght of informations and number of photos will vary, all is nested in UIScrollView.
Photos are shown in UICollectionView, but I need to always show all contained photos, so I disabled scrolling and dynamically changing Height constraint of UICollectionView by this function (called when finishing rendering cells):
func resizePhotosCollectionView() {
photosCollectionViewHeightConstraint.constant = photosCollectionView.collectionViewLayout.collectionViewContentSize()
}
It works great until this UICollectionView needs resize (typically by device orientation change). I am trying to use UIViewControllerTransitionCoordinator in function:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { context in
self.resizePhotosCollectionView()
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
but result is jerky because Height constraint changed after transition is complete.
Is there any way how to automatically resize UICollectionView after view transition? If not, how to animate Height constraint change during transition?
Using Xcode 6.1 for target IOS 8.
If I recall correctly, you need to layout the view immediately.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { context in
self.resizePhotosCollectionView()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
Have you tried wrapping your constraint update into a view animation?
photosCollectionViewHeightConstraint.constant =
photosCollectionView.collectionViewLayout.collectionViewContentSize()
photosCollectionView.setNeedsUpdateConstraints()
UIView.animateWithDuration(0.3, animations: { () -> Void in
photosCollectionView.layoutIfNeeded()
})