在 UICollectionView 中将单元格居中(水平)对齐
Align cell in center(horizontally) in UICollectionView
我是第一次使用 UICollectionView,不知道该怎么做。我正在尝试为 tvOS 创建一个应用程序,并希望像 airbnb tvos 应用程序一样显示菜单。我以某种方式尝试实现该特定格式 didUpdateFocusInContext
但问题是关于第一次出现,因为第一次出现发生在默认点,即集合视图的 0,0,这导致一团糟。
这是我到目前为止所做的代码。
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as? ShowCell {
menuData = dataArray[indexPath.row] as! NSDictionary
cell.configureCell(menuData.objectForKey("name") as! String)
return cell
}
else {
return ShowCell()
}
}
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if let previousItem = context.previouslyFocusedView as? ShowCell {
UIView.animateWithDuration(0.2, animations: { () -> Void in
previousItem.showImg.frame.size = self.originalCellSize
})
}
if let nextItem = context.nextFocusedView as? ShowCell {
UIView.animateWithDuration(0.2, animations: { () -> Void in
nextItem.showImg.frame = CGRectMake(
self.view.frame.width/2 - self.focusCellSize.width/2,
169.0,
self.focusCellSize.width,
self.focusCellSize.height)
})
}
}
这是我想要实现的视图,我已经实现了但是对于后面的索引意味着索引在 1,2
之后
这是它第一次出现时的起始行为,当我将控件移到它上面时,它会像这样发生
这正是我真正想要的,但我正在努力让我的焦点单元格位于屏幕中间,同样地,我的前一个和下一个位于两边。我知道我明确地给出了帧坐标,这是不正确的这只是一个测试用例,我是 运行 没有别的,但我找不到这样做的方法
我会分别控制焦点和 collection 内容偏移(滚动位置)。
对于内容偏移,您应该设置节边距和 inter-item 间距,这样您就可以使一个单元格居中,相邻单元格在边缘可见。您可以在不显示任何焦点的情况下获得此设置并进行测试。
当您滚动(焦点改变)时,可能很难让项目准确地移动到中心。要解决此问题,请实施 - scrollViewWillEndDragging:withVelocity:targetContentOffset:
以在 targetContentOffset
处查找项目并获取其中心点(来自布局属性)。有了它,您可以修改 targetContentOffset
,使滚动完全以项目为中心结束。
现在,焦点应该由单元格本身管理,而不是 collection 视图。下面是一个(稍大)的单元格焦点更改动画示例。它使用制图来更改图像视图约束并将转换应用于标签。您可以根据您希望图像和标签如何相互交互来做类似的事情。
请注意,当 UIImageView
具有焦点并设置了 adjustsImageWhenAncestorFocused
时,下面的代码还应用了类似于苹果提供的股票的运动效果变换。如果您不希望这样,您可以大大简化和缩短代码。
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)
if (context.nextFocusedView == self) {
UIView.animateWithDuration(0.1,
animations: { () -> Void in
self.imageConstraints = constrain(self.itemImageView, replace: self.imageConstraints!) {
[=10=].top == [=10=].superview!.top
[=10=].bottom == [=10=].superview!.bottom
[=10=].leading == [=10=].superview!.leading
[=10=].trailing == [=10=].superview!.trailing
}
self.itemLabel.transform = CGAffineTransformMakeTranslation(0, 60)
self.itemLabel.layer.backgroundColor = UIColor.darkGrayColor().colorWithAlphaComponent(0).CGColor
self.layer.shadowOpacity = 1
self.layoutIfNeeded()
}, completion: nil)
let minMaxAngle = 10.0
let m34 = CGFloat(1.0 / -1250)
let angle = CGFloat(minMaxAngle * M_PI / 180.0)
var baseTransform = CATransform3DIdentity
baseTransform.m34 = m34
let rotateXmin = CATransform3DRotate(baseTransform, -1 * angle, 1.0, 0.0, 0.0);
let rotateXmax = CATransform3DRotate(baseTransform, angle, 1.0, 0.0, 0.0);
let rotateYmin = CATransform3DRotate(baseTransform, angle, 0.0, 1.0, 0.0);
let rotateYmax = CATransform3DRotate(baseTransform, -1 * angle, 0.0, 1.0, 0.0);
let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform",
type: .TiltAlongVerticalAxis)
verticalMotionEffect.minimumRelativeValue = NSValue(CATransform3D: rotateXmin)
verticalMotionEffect.maximumRelativeValue = NSValue(CATransform3D: rotateXmax)
let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform",
type: .TiltAlongHorizontalAxis)
horizontalMotionEffect.minimumRelativeValue = NSValue(CATransform3D: rotateYmin)
horizontalMotionEffect.maximumRelativeValue = NSValue(CATransform3D: rotateYmax)
let group = UIMotionEffectGroup()
group.motionEffects = [horizontalMotionEffect, verticalMotionEffect]
self.addMotionEffect(group)
}
else {
UIView.animateWithDuration(0.3,
animations: { () -> Void in
self.imageConstraints = constrain(self.itemImageView, replace: self.imageConstraints!) {
[=10=].top == [=10=].superview!.top + 20
[=10=].bottom == [=10=].superview!.bottom - 20
[=10=].leading == [=10=].superview!.leading + 20
[=10=].trailing == [=10=].superview!.trailing - 20
}
self.itemLabel.transform = CGAffineTransformIdentity
self.itemLabel.layer.backgroundColor = UIColor.darkGrayColor().colorWithAlphaComponent(0.75).CGColor
self.layer.shadowOpacity = 0
self.layoutIfNeeded()
}, completion: nil)
for effect in self.motionEffects {
self.removeMotionEffect(effect)
}
}
}
@Wain 已经制定了实现上述风格的指南,这是我对此的准确回答:
override func didUpdateFocusInContext(context: UIFocusUpdateContext,
withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if let focusedView = context.nextFocusedView as? ShowCell {
collectionView.scrollEnabled = false
let indexPath = collectionView.indexPathForCell(focusedView)!
focusedView.showImg.frame.size = self.focusImageSize
focusedView.showLbl.frame.size = self.focusLabelSize
focusedView.showLbl.font = UIFont.systemFontOfSize(75)
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .CenteredHorizontally, animated: true)
self.collectionView.layoutIfNeeded()
}
else if let lastFocuedView = context.previouslyFocusedView as? ShowCell{
collectionView.scrollEnabled = true
// let indexPath = collectionView.indexPathForCell(lastFocuedView)!
lastFocuedView.showImg.frame.size = self.originalImageSize
lastFocuedView.showLbl.frame.size = self.originalLabelSize
lastFocuedView.showLbl.font = UIFont.systemFontOfSize(70)
self.collectionView.layoutIfNeeded()
}
并且您必须为第一个和最后一个单元格提供 UIEdgeInsets(它是流布局的委托方法)
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets{
return UIEdgeInsets(top: 0.0, left: self.collectionView.frame.width/2 , bottom: 0.0, right: self.collectionView.frame.width/2)
}
衷心感谢 Wain 为这个问题提供了准确的指导。
干杯!
解决方法是在scrollViewWillEndDragging
这里覆盖滚动视图委托方法 (Swift 3)
我是第一次使用 UICollectionView,不知道该怎么做。我正在尝试为 tvOS 创建一个应用程序,并希望像 airbnb tvos 应用程序一样显示菜单。我以某种方式尝试实现该特定格式 didUpdateFocusInContext
但问题是关于第一次出现,因为第一次出现发生在默认点,即集合视图的 0,0,这导致一团糟。
这是我到目前为止所做的代码。
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as? ShowCell {
menuData = dataArray[indexPath.row] as! NSDictionary
cell.configureCell(menuData.objectForKey("name") as! String)
return cell
}
else {
return ShowCell()
}
}
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if let previousItem = context.previouslyFocusedView as? ShowCell {
UIView.animateWithDuration(0.2, animations: { () -> Void in
previousItem.showImg.frame.size = self.originalCellSize
})
}
if let nextItem = context.nextFocusedView as? ShowCell {
UIView.animateWithDuration(0.2, animations: { () -> Void in
nextItem.showImg.frame = CGRectMake(
self.view.frame.width/2 - self.focusCellSize.width/2,
169.0,
self.focusCellSize.width,
self.focusCellSize.height)
})
}
}
这是我想要实现的视图,我已经实现了但是对于后面的索引意味着索引在 1,2
这是它第一次出现时的起始行为,当我将控件移到它上面时,它会像这样发生
这正是我真正想要的,但我正在努力让我的焦点单元格位于屏幕中间,同样地,我的前一个和下一个位于两边。我知道我明确地给出了帧坐标,这是不正确的这只是一个测试用例,我是 运行 没有别的,但我找不到这样做的方法
我会分别控制焦点和 collection 内容偏移(滚动位置)。
对于内容偏移,您应该设置节边距和 inter-item 间距,这样您就可以使一个单元格居中,相邻单元格在边缘可见。您可以在不显示任何焦点的情况下获得此设置并进行测试。
当您滚动(焦点改变)时,可能很难让项目准确地移动到中心。要解决此问题,请实施 - scrollViewWillEndDragging:withVelocity:targetContentOffset:
以在 targetContentOffset
处查找项目并获取其中心点(来自布局属性)。有了它,您可以修改 targetContentOffset
,使滚动完全以项目为中心结束。
现在,焦点应该由单元格本身管理,而不是 collection 视图。下面是一个(稍大)的单元格焦点更改动画示例。它使用制图来更改图像视图约束并将转换应用于标签。您可以根据您希望图像和标签如何相互交互来做类似的事情。
请注意,当 UIImageView
具有焦点并设置了 adjustsImageWhenAncestorFocused
时,下面的代码还应用了类似于苹果提供的股票的运动效果变换。如果您不希望这样,您可以大大简化和缩短代码。
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)
if (context.nextFocusedView == self) {
UIView.animateWithDuration(0.1,
animations: { () -> Void in
self.imageConstraints = constrain(self.itemImageView, replace: self.imageConstraints!) {
[=10=].top == [=10=].superview!.top
[=10=].bottom == [=10=].superview!.bottom
[=10=].leading == [=10=].superview!.leading
[=10=].trailing == [=10=].superview!.trailing
}
self.itemLabel.transform = CGAffineTransformMakeTranslation(0, 60)
self.itemLabel.layer.backgroundColor = UIColor.darkGrayColor().colorWithAlphaComponent(0).CGColor
self.layer.shadowOpacity = 1
self.layoutIfNeeded()
}, completion: nil)
let minMaxAngle = 10.0
let m34 = CGFloat(1.0 / -1250)
let angle = CGFloat(minMaxAngle * M_PI / 180.0)
var baseTransform = CATransform3DIdentity
baseTransform.m34 = m34
let rotateXmin = CATransform3DRotate(baseTransform, -1 * angle, 1.0, 0.0, 0.0);
let rotateXmax = CATransform3DRotate(baseTransform, angle, 1.0, 0.0, 0.0);
let rotateYmin = CATransform3DRotate(baseTransform, angle, 0.0, 1.0, 0.0);
let rotateYmax = CATransform3DRotate(baseTransform, -1 * angle, 0.0, 1.0, 0.0);
let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform",
type: .TiltAlongVerticalAxis)
verticalMotionEffect.minimumRelativeValue = NSValue(CATransform3D: rotateXmin)
verticalMotionEffect.maximumRelativeValue = NSValue(CATransform3D: rotateXmax)
let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform",
type: .TiltAlongHorizontalAxis)
horizontalMotionEffect.minimumRelativeValue = NSValue(CATransform3D: rotateYmin)
horizontalMotionEffect.maximumRelativeValue = NSValue(CATransform3D: rotateYmax)
let group = UIMotionEffectGroup()
group.motionEffects = [horizontalMotionEffect, verticalMotionEffect]
self.addMotionEffect(group)
}
else {
UIView.animateWithDuration(0.3,
animations: { () -> Void in
self.imageConstraints = constrain(self.itemImageView, replace: self.imageConstraints!) {
[=10=].top == [=10=].superview!.top + 20
[=10=].bottom == [=10=].superview!.bottom - 20
[=10=].leading == [=10=].superview!.leading + 20
[=10=].trailing == [=10=].superview!.trailing - 20
}
self.itemLabel.transform = CGAffineTransformIdentity
self.itemLabel.layer.backgroundColor = UIColor.darkGrayColor().colorWithAlphaComponent(0.75).CGColor
self.layer.shadowOpacity = 0
self.layoutIfNeeded()
}, completion: nil)
for effect in self.motionEffects {
self.removeMotionEffect(effect)
}
}
}
@Wain 已经制定了实现上述风格的指南,这是我对此的准确回答:
override func didUpdateFocusInContext(context: UIFocusUpdateContext,
withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if let focusedView = context.nextFocusedView as? ShowCell {
collectionView.scrollEnabled = false
let indexPath = collectionView.indexPathForCell(focusedView)!
focusedView.showImg.frame.size = self.focusImageSize
focusedView.showLbl.frame.size = self.focusLabelSize
focusedView.showLbl.font = UIFont.systemFontOfSize(75)
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .CenteredHorizontally, animated: true)
self.collectionView.layoutIfNeeded()
}
else if let lastFocuedView = context.previouslyFocusedView as? ShowCell{
collectionView.scrollEnabled = true
// let indexPath = collectionView.indexPathForCell(lastFocuedView)!
lastFocuedView.showImg.frame.size = self.originalImageSize
lastFocuedView.showLbl.frame.size = self.originalLabelSize
lastFocuedView.showLbl.font = UIFont.systemFontOfSize(70)
self.collectionView.layoutIfNeeded()
}
并且您必须为第一个和最后一个单元格提供 UIEdgeInsets(它是流布局的委托方法)
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets{
return UIEdgeInsets(top: 0.0, left: self.collectionView.frame.width/2 , bottom: 0.0, right: self.collectionView.frame.width/2)
}
衷心感谢 Wain 为这个问题提供了准确的指导。 干杯!
解决方法是在scrollViewWillEndDragging
这里覆盖滚动视图委托方法 (Swift 3)