Ответ 1
Добавьте эту функцию:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myCollection.collectionViewLayout.invalidateLayout()
}
Когда вы меняете ориентацию, эта функция будет вызываться.
Я использовал UICollectionView (flowlayout), чтобы создать простой макет.
ширина для каждой ячейки установлена на ширину экрана с помощью self.view.frame.width
но когда я поворачиваю устройство, ячейки не обновляются.
Я нашел функцию, которая называется изменением ориентации:
override func willRotateToInterfaceOrientation(toInterfaceOrientation:
UIInterfaceOrientation, duration: NSTimeInterval) {
//code
}
но я не могу найти способ обновить макет UICollectionView
Основной код здесь:
class ViewController: UIViewController , UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout{
@IBOutlet weak var myCollection: UICollectionView!
var numOfItemsInSecOne: Int!
override func viewDidLoad() {
super.viewDidLoad()
numOfItemsInSecOne = 8
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
//print("orientation Changed")
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numOfItemsInSecOne
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellO", forIndexPath: indexPath)
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
let itemSize = CGSize(width: self.view.frame.width, height: 100)
return itemSize
}}
Добавьте эту функцию:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myCollection.collectionViewLayout.invalidateLayout()
}
Когда вы меняете ориентацию, эта функция будет вызываться.
Лучшим вариантом является вызов invalidateLayout()
вместо reloadData()
, потому что он не будет принудительно восстанавливать ячейки, поэтому производительность будет немного лучше:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
myCollection.collectionViewLayout.invalidateLayout()
}
Также вы можете сделать это недействительным.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.collectionView.collectionViewLayout invalidateLayout];
}
ViewWillLayoutSubviews() не работает для меня. Также не было viewDidLayoutSubviews(). Оба приложения превратились в бесконечный цикл, который я проверил с помощью команды печати.
Один из способов работы -
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Reload here
}
для обновления метода UICollectionViewLayout
traitCollectionDidChange
может быть использован:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard let previousTraitCollection = previousTraitCollections else {
return
}
collectionView?.collectionViewLayout.invalidateLayout()
}
вы можете обновить макет UICollectionView с помощью
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if isLandscape {
return CGSizeMake(yourLandscapeWidth, yourLandscapeHeight)
}
else {
return CGSizeMake(yourNonLandscapeWidth, yourNonLandscapeHeight)
}
}
У меня также была какая-то проблема, но потом она была решена с помощью:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionViewFlowLayoutSetup(with: view.bounds.size.width)
collectionView?.collectionViewLayout.invalidateLayout()
collectionViewFlowLayoutSetup(with: size.width)
}
fileprivate func collectionViewFlowLayoutSetup(with Width: CGFloat){
if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: Width, height: 300)
}
}
Я решаю эту проблему, устанавливая уведомление при изменении ориентации экрана и перезагружая ячейку, которая устанавливает размер элементов в соответствии с ориентацией экрана и задает путь индекса к предыдущей ячейке. Это тоже работает с flowlayout. Вот код, который я написал:
var cellWidthInLandscape: CGFloat = 0 {
didSet {
self.collectionView.reloadData()
}
}
var lastIndex: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
cellWidthInLandscape = UIScreen.main.bounds.size.width
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func rotated() {
// Setting new width on screen orientation change
cellWidthInLandscape = UIScreen.main.bounds.size.width
// Setting collectionView to previous indexpath
collectionView.scrollToItem(at: IndexPath(item: lastIndex, section: 0), at: .right, animated: false)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// Getting last contentOffset to calculate last index of collectionViewCell
lastIndex = Int(scrollView.contentOffset.x / collectionView.bounds.width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Setting new width of collectionView Cell
return CGSize(width: cellWidthInLandscape, height: collectionView.bounds.size.height)
}
Когда UICollectionLayout
обнаруживает изменение границ, он спрашивает, нужно ли перенаправить макет Invalidate. Вы можете переписать метод напрямую. UICollectionLayout
может вызвать метод invalidateLayout
в нужное время
class CollectionViewFlowLayout: UICollectionViewFlowLayout{
/// The default implementation of this method returns false.
/// Subclasses can override it and return an appropriate value
/// based on whether changes in the bounds of the collection
/// view require changes to the layout of cells and supplementary views.
/// If the bounds of the collection view change and this method returns true,
/// the collection view invalidates the layout by calling the invalidateLayout(with:) method.
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return (self.collectionView?.bounds ?? newBounds) == newBounds
}
}
Вызов viewWillLayoutSubviews
не является оптимальным. Попробуйте сначала вызвать метод invalidateLayout()
.
Если вы столкнулись с ошибкой The behaviour of the UICollectionViewFlowLayout is not defined
, вам необходимо проверить, все ли элементы в вашем представлении изменили свои размеры в соответствии с новым макетом. (см. дополнительные шаги в примере кода)
Вот код, чтобы вы начали. В зависимости от того, как создан ваш пользовательский интерфейс, вам, возможно, придется поэкспериментировать, чтобы найти правильное представление для вызова метода recalculate
, но это должно привести вас к вашим первым шагам.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
/// (Optional) Additional step 1. Depending on your layout, you may have to manually indicate that the content size of a visible cells has changed
/// Use that step if you experience the 'the behavior of the UICollectionViewFlowLayout is not defined' errors.
collectionView.visibleCells.forEach { cell in
guard let cell = cell as? CustomCell else {
print("'viewWillTransition' failed. Wrong cell type")
return
}
cell.recalculateFrame(newSize: size)
}
/// (Optional) Additional step 2. Recalculate layout if you've explicitly set the estimatedCellSize and you'll notice that layout changes aren't automatically visible after the #3
(collectionView.collectionViewLayout as? CustomLayout)?.recalculateLayout(size: size)
/// Step 3 (or 1 if none of the above is applicable)
coordinator.animate(alongsideTransition: { context in
self.collectionView.collectionViewLayout.invalidateLayout()
}) { _ in
// code to execute when the transition finished.
}
}
/// Example implementations of the 'recalculateFrame' and 'recalculateLayout' methods:
/// Within the 'CustomCell' class:
func recalculateFrame(newSize: CGSize) {
self.frame = CGRect(x: self.bounds.origin.x,
y: self.bounds.origin.y,
width: newSize.width - 14.0,
height: self.frame.size.height)
}
/// Within the 'CustomLayout' class:
func recalculateLayout(size: CGSize? = nil) {
estimatedItemSize = CGSize(width: size.width - 14.0, height: 100)
}
/// IMPORTANT: Within the 'CustomLayout' class.
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
guard let collectionView = collectionView else {
return super.shouldInvalidateLayout(forBoundsChange: newBounds)
}
if collectionView.bounds.width != newBounds.width || collectionView.bounds.height != newBounds.height {
return true
} else {
return false
}
}
Я решил проблему, используя метод ниже
override func viewDidLayoutSubviews() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
collectionView.collectionViewLayout.invalidateLayout()
collectionView.collectionViewLayout = flowLayout
}
}
Меня устраивает. И это код в Objective-C:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[collectionView.collectionViewLayout invalidateLayout];
}