UICollectionView - 在设备旋转时调整单元格大小 - Swift
UICollectionView - resizing cells on device rotate - Swift
我创建了一个 UICollectionView,这样我就可以将视图排列成整齐的列。我希望设备上有一个单列 > 500 像素宽。
为了实现这个,我创建了这个函数:
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)
}
这在第一次加载时按预期工作,但是当我旋转设备时,计算并不总是再次发生,并且视图并不总是按预期重绘。这是我的设备旋转时的代码:
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
collectionView.collectionViewLayout.invalidateLayout()
self.view.setNeedsDisplay()
}
我假设我忘记重画一些东西,但我不确定是什么。非常感谢收到任何想法!
您可能会使用 viewWillLayoutSubviews
。 This question 应该会有帮助,但只要视图控制器视图即将布局其子视图,就会调用它。
因此您的代码将如下所示:
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()
}
我倾向于将 viewWillTransitionToSize
用于相同的东西,并简单地在其中调用 invalidateLayout()
。
也许最直接的方法是在 viewWillTransitionToSize 期间使 invalidateLayout:
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()
}
我的任务是调整 UICollectionView 子类中的项目大小。最好的方法是 setFrame。 属性 我从 ViewController 传递的 collectionViewFlowLayout(在我的例子中它是默认流布局的出口)。
// .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];
}
也可以使用 traitCollectionDidChange
方法代替 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()
}
您可以创建遮罩内容并将其移动到 collectionView。 landscape/portrait动画结束后,必须尽快将其移除。
这是一个例子:
@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];
}];
}
我使用了以下方法,对我有用。
我的问题是我在
中使布局无效
viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
但此时(正如此方法名称所示)设备尚未旋转。
所以这个方法将在 viewWillLayoutSubviews
之前调用,因此在这个方法中我们没有正确的边界和框架(安全区域),因为设备将在之后旋转。
所以我使用了通知
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()
}
然后在集合视图流委托方法中,一切都按预期工作。
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)
}
}
}
要让集合视图调整其单元格的大小,并在旋转期间使更改动画化,请使用过渡协调器:
(Swift 4+)
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// Have the collection view re-layout its cells.
coordinator.animate(
alongsideTransition: { _ in self.collectionView.collectionViewLayout.invalidateLayout() },
completion: { _ in }
)
}
这适用于可能有相同特殊情况的其他人。
情况:
我在 table 视图控制器中包含了一个 UICollectionView
作为 UITableViewCell
。行高设置为 UITableViewAutomaticDimension
以适应集合视图中的单元格数量。尽管同一 table 视图中具有动态内容的其他单元格表现正确,但它在设备旋转时布局不正确。经过长时间的研究,我找到了一个有效的解决方案:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self reloadTableViewSilently];
}
- (void) reloadTableViewSilently {
dispatch_async(dispatch_get_main_queue(), ^{
// optional: [UIView setAnimationsEnabled:false];
[self.tableView beginUpdates];
[self.tableView endUpdates];
// optional: [UIView setAnimationsEnabled:true];
});
}
确保您的 Cell 已设置 属性 autoResizingMask。
示例:
newImage.autoresizingMask = [.flexibleWidth, .flexibleHeight]
我创建了一个 UICollectionView,这样我就可以将视图排列成整齐的列。我希望设备上有一个单列 > 500 像素宽。
为了实现这个,我创建了这个函数:
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)
}
这在第一次加载时按预期工作,但是当我旋转设备时,计算并不总是再次发生,并且视图并不总是按预期重绘。这是我的设备旋转时的代码:
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
collectionView.collectionViewLayout.invalidateLayout()
self.view.setNeedsDisplay()
}
我假设我忘记重画一些东西,但我不确定是什么。非常感谢收到任何想法!
您可能会使用 viewWillLayoutSubviews
。 This question 应该会有帮助,但只要视图控制器视图即将布局其子视图,就会调用它。
因此您的代码将如下所示:
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()
}
我倾向于将 viewWillTransitionToSize
用于相同的东西,并简单地在其中调用 invalidateLayout()
。
也许最直接的方法是在 viewWillTransitionToSize 期间使 invalidateLayout:
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()
}
我的任务是调整 UICollectionView 子类中的项目大小。最好的方法是 setFrame。 属性 我从 ViewController 传递的 collectionViewFlowLayout(在我的例子中它是默认流布局的出口)。
// .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];
}
traitCollectionDidChange
方法代替 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()
}
您可以创建遮罩内容并将其移动到 collectionView。 landscape/portrait动画结束后,必须尽快将其移除。
这是一个例子:
@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];
}];
}
我使用了以下方法,对我有用。 我的问题是我在
中使布局无效viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
但此时(正如此方法名称所示)设备尚未旋转。
所以这个方法将在 viewWillLayoutSubviews
之前调用,因此在这个方法中我们没有正确的边界和框架(安全区域),因为设备将在之后旋转。
所以我使用了通知
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()
}
然后在集合视图流委托方法中,一切都按预期工作。
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)
}
}
}
要让集合视图调整其单元格的大小,并在旋转期间使更改动画化,请使用过渡协调器:
(Swift 4+)
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// Have the collection view re-layout its cells.
coordinator.animate(
alongsideTransition: { _ in self.collectionView.collectionViewLayout.invalidateLayout() },
completion: { _ in }
)
}
这适用于可能有相同特殊情况的其他人。
情况:
我在 table 视图控制器中包含了一个 UICollectionView
作为 UITableViewCell
。行高设置为 UITableViewAutomaticDimension
以适应集合视图中的单元格数量。尽管同一 table 视图中具有动态内容的其他单元格表现正确,但它在设备旋转时布局不正确。经过长时间的研究,我找到了一个有效的解决方案:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self reloadTableViewSilently];
}
- (void) reloadTableViewSilently {
dispatch_async(dispatch_get_main_queue(), ^{
// optional: [UIView setAnimationsEnabled:false];
[self.tableView beginUpdates];
[self.tableView endUpdates];
// optional: [UIView setAnimationsEnabled:true];
});
}
确保您的 Cell 已设置 属性 autoResizingMask。
示例:
newImage.autoresizingMask = [.flexibleWidth, .flexibleHeight]