programing

Swift: UICollection을 새로 고치는 방법장치 회전 후 레이아웃 보기

muds 2023. 8. 29. 21:03
반응형

Swift: UICollection을 새로 고치는 방법장치 회전 후 레이아웃 보기

UICollectionView(플로 레이아웃)를 사용하여 간단한 레이아웃을 작성했습니다.각 셀의 너비는 다음을 사용하여 화면의 너비로 설정됩니다.self.view.frame.width

하지만 장치를 돌리면 셀이 업데이트되지 않습니다.

enter image description here

방향 변경 시 호출되는 함수를 찾았습니다.

override func willRotateToInterfaceOrientation(toInterfaceOrientation: 
  UIInterfaceOrientation, duration: NSTimeInterval) {
    //code
}

그러나 UICollectView 레이아웃을 업데이트할 수 없습니다.

주요 코드는 다음과 같습니다.

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]; 
}

WillLayoutSubviews() 보기가 작동하지 않았습니다.DidLayoutSubviews()도 볼 수 없습니다.둘 다 앱을 무한 루프 상태로 만들어 인쇄 명령을 사용하여 확인했습니다.

작동하는 방법 중 하나는

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Reload here
}

언제UICollectionLayout경계 변경을 감지하면 유효하지 않은 레이아웃의 경로를 다시 지정해야 하는지 여부를 묻습니다.메소드를 직접 다시 작성할 수 있습니다.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
    }
}

업데이트하기UICollectionViewLayout,traitCollectionDidChange방법을 사용할 수도 있습니다.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)

    guard previousTraitCollection != nil else { return }
    collectionView?.collectionViewLayout.invalidateLayout()
}

이 점 양해 부탁드립니다.

traitCollectionDidChange(이전의 traitCollection :)는 크기 클래스가 세로 방향과 가로 방향 모두에서 .정규이기 때문에 회전할 때 iPad에서 호출되지 않습니다.컬렉션 뷰 크기가 변경될 때마다 호출되는 WillTransition(to:with:) 뷰가 있습니다.

또한 앱이 전체 화면을 차지하지 않을 수 있으므로 멀티태스킹을 지원하는 경우에는 UIScreen.mainScreen().bounds를 사용하지 마십시오. collectionView.frame을 사용하는 것이 좋습니다.그것을 위한 폭.

내 코드:

override func viewWillLayoutSubviews() {
   super.viewWillLayoutSubviews()
   self.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
        }
    }

다음을 사용하여 UICollectionView 레이아웃을 업데이트할 수 있습니다.

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    if isLandscape {
        return CGSizeMake(yourLandscapeWidth, yourLandscapeHeight)
    }
    else {
        return CGSizeMake(yourNonLandscapeWidth, yourNonLandscapeHeight)
    }
}

저한테는 효과가 있어요.그리고 이것은 목표-C의 코드는 다음과 같습니다.

- (void)viewDidLayoutSubviews {
  [super viewDidLayoutSubviews];
  [collectionView.collectionViewLayout invalidateLayout];
}

저도 약간의 문제가 있었지만 다음을 사용하여 해결되었습니다.

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)
        }

    }

화면 방향이 바뀌면 알림을 설정하고 화면 방향에 따라 항목 크기를 설정하는 셀을 다시 로드하고 이전 셀에 인덱스 경로를 설정하여 해결합니다.이것은 흐름 레이아웃에서도 작동합니다.제가 작성한 코드는 다음과 같습니다.

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)

}

동시에 선택한 셀은 가로 방향에서 세로 방향으로 변경된 후 중앙에 배치되지 않습니다.저는 이 문제를 다음과 같이 해결합니다.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    //Scroll to current Item after the orientation change. Sometimes the current item is not centered.
    coordinator.animate(alongsideTransition: { context in
        self.housesCollectionView.collectionViewLayout.invalidateLayout()
        self.housesCollectionView.scrollToItem(at: self.selectedHouseIndexPath, at: .left, animated: true)
    })
}

아래 방법으로 문제를 해결했습니다.

override func viewDidLayoutSubviews() {
        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            collectionView.collectionViewLayout.invalidateLayout()
            collectionView.collectionViewLayout = flowLayout
        }
    }

다른 모든 사용자가 이미 언급한 내용 외에도 UICollectionViewController를 하위 분류하는 대신 직접 CollectionView를 작성하는 경우 다음을 설정하는 것을 잊지 마십시오.

collectionView.translatesAutoresizingMaskIntoConstraints = false

제약 조건을 설정할 때.

사용해 보십시오.

class CollectionViewFlowLayout: UICollectionViewFlowLayout {

override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
    let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
    if let collectionView = collectionView {
        context.invalidateFlowLayoutDelegateMetrics = collectionView.bounds.size != newBounds.size
    }
    return context
  }

}

설명서:

이 속성의 기본값은 false입니다.항목의 크기가 변경되어 레이아웃을 무효화하는 경우 이 속성을 true로 설정합니다.이 속성을 true로 설정하면 흐름 레이아웃 개체가 항목 및 보기의 크기를 다시 계산하여 필요에 따라 위임 개체를 쿼리합니다.

언급URL : https://stackoverflow.com/questions/36884376/swift-how-to-refresh-uicollectionview-layout-after-rotation-of-the-device

반응형