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()もしませんでした。どちらもアプリを無限ループに陥らせ、印刷コマンドを使用してチェックしました。
動作する方法の1つは
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)
}
}
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
}
}
私もいくつかの問題を抱えていましたが、それを使用して解決しました:
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)
}
わたしにはできる。そして、これはObjective-Cのコードです:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[collectionView.collectionViewLayout invalidateLayout];
}
viewWillLayoutSubviews
の呼び出しは最適ではありません。最初にinvalidateLayout()
メソッドを呼び出してみてください。
The behaviour of the UICollectionViewFlowLayout is not defined
エラーが発生した場合は、新しいレイアウトに従って、ビュー内のすべての要素のサイズが変更されたかどうかを確認する必要があります。 (サンプルコード内のオプションの手順を参照してください)
始めるためのコードは次のとおりです。 UIの作成方法によっては、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's 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
}
}