可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've created a UICollectionView, so that I can arrange views into neat columns. I'd like there to be a single column on devices > 500 pixels wide.
In order to achieve this, I created this function:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let size = collectionView.frame.width
if (size > 500) {
return CGSize(width: (size/2) - 8, height: (size/2) - 8)
}
return CGSize(width: size, height: size)
}
This works as expected on first load, however when I rotate the device, the calculation doesn't always happen again, and the views don't always redraw as expected. Here's my code for when the device is rotated:
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
collectionView.collectionViewLayout.invalidateLayout()
self.view.setNeedsDisplay()
}
I'm assuming I've forgotten to redraw something, but I'm not sure what. Any ideas are very gratefully recieved!
回答1:
You might use viewWillLayoutSubviews
. This question should be helpful but this is bassically called whenever the view controller views is about to layout its subviews.
So your code will look like this:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) {
//here you can do the logic for the cell size if phone is in landscape
} else {
//logic if not landscape
}
flowLayout.invalidateLayout()
}
回答2:
Perhaps the most straight way to make this is to invalidateLayout during the viewWillTransitionToSize:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
flowLayout.invalidateLayout()
}
回答3:
I've tended to use viewWillTransitionToSize
with the same thing and simply made a call invalidateLayout()
in there.
回答4:
traitCollectionDidChange
method might be used as well instead of viewWillLayoutSubviews
:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard let previousTraitCollection = previousTraitCollection, traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass ||
traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass else {
return
}
if traitCollection.horizontalSizeClass == .regular && traitCollection.verticalSizeClass == .regular {
// iPad portrait and landscape
// do something here...
}
if traitCollection.horizontalSizeClass == .compact && traitCollection.verticalSizeClass == .regular {
// iPhone portrait
// do something here...
}
if traitCollection.horizontalSizeClass == .regular && traitCollection.verticalSizeClass = .compact {
// iPhone landscape
// do something here...
}
collectionView?.collectionViewLayout.invalidateLayout()
collectionView?.reloadData()
}
回答5:
I have used following approach, which worked for me.
Problem with me was that I was invalidating layout in
viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
But at this point (as this method name suggest) Device has not been rotated yet.
So this method will be called before viewWillLayoutSubviews
and hence in this method we do not have right bounds and frames (Safe Area) as device will rotate afterwards.
So I used notifications
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}
@objc func rotated(){
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
flowLayout.invalidateLayout()
}
and then in collection view flow delegate method everything works as expected.
extension ViewController: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
if #available(iOS 11.0, *) {
return CGSize(width: view.safeAreaLayoutGuide.layoutFrame.width, height: 70)
} else {
return CGSize(width: view.frame.width, height: 70)
}
}
}
回答6:
I had a task to adjust items size in UICollectionView subclass. The best method for this is setFrame. Property collectionViewFlowLayout I passed from ViewController (in my case it was outlet from default flow layout).
// .h
@property (nonatomic, weak) UICollectionViewFlowLayout *collectionViewFlowLayout;
// .m
- (void)setFrame:(CGRect)frame {
if (!CGSizeEqualToSize(frame.size, self.frame.size)) {
collectionViewFlowLayout.itemSize =
CGSizeMake((UIDeviceOrientationIsLandscape(UIDevice.currentDevice.orientation) ? (frame.size.width - 10) / 2 : frame.size.width), collectionViewFlowLayout.itemSize.height);
}
[super setFrame:frame];
}
回答7:
You can create a mask content and move it to the collectionView. After the landscape/portrait animation is finished, you must remove it ASAP.
Here is an example:
@property (strong, nonatomic) UIImageView *maskImage;
.........
- (UIImageView *) imageForCellAtIndex: (NSInteger) index {
UICollectionView *collectionView = self.pagerView.test;
FSPagerViewCell *cell = nil;
NSArray *indexPaths = [collectionView indexPathsForVisibleItems];
for (NSIndexPath *indexPath in indexPaths) {
if (indexPath.item == index) {
cell = (FSPagerViewCell *)[collectionView cellForItemAtIndexPath: indexPath];
break;
}
}
if (cell) {
return cell.imageView;
}
return nil;
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
[self test_didRotateFromInterfaceOrientation: orientation];
UIImageView *imageView = [self imageForCellAtIndex: self.pagerView.currentIndex];
if (imageView) {
UIImageView *imageView = [self imageForCellAtIndex: self.pagerView.currentIndex];
CGSize itemSize = self.pagerView.itemSize;
UIImageView *newImage = [[UIImageView alloc] initWithImage: imageView.image];
[newImage setFrame: CGRectMake((_contentView.bounds.size.width - itemSize.width)/2.0f, 0, itemSize.width, itemSize.height)];
newImage.contentMode = imageView.contentMode;
newImage.clipsToBounds = imageView.clipsToBounds;
newImage.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.pagerView addSubview: newImage];
self.maskImage = newImage;
}
[self.pagerView.test performBatchUpdates:^{
[self.pagerView.test setCollectionViewLayout:self.pagerView.test.collectionViewLayout animated:YES];
} completion:nil];
// do whatever
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
[self.maskImage removeFromSuperview];
}];
}