我在UITableViewCell中实现了一个UIScrollView,使用户能够以与iOS Mail应用程序相同的方式向左和向右滚动以显示按钮。设置框架和位置的原始实现明确地运行良好,但我重构了代码以使用autolayout。动画隐藏/显示'容器'对于左侧的按钮(附件按钮)效果很好,但是当右侧容器(编辑按钮)在到达其最终位置之前到达所需偏移之前减慢时,动画会使滚动视图停止。
所有计算都使用刚刚转换的相同数学(例如+而不是 - 值,>而不是<在测试中),具体取决于容器所在的一侧以及记录显示的值是否正确。我无法看到任何明显的代码错误,并且对IB中设置的单元没有任何限制。这是一个错误还是有一些显而易见的东西我通过盯着最后一小时的代码而错过了?
class SwipeyTableViewCell: UITableViewCell {
// MARK: Constants
private let thresholdVelocity = CGFloat(0.6)
private let maxClosureDuration = CGFloat(40)
// MARK: Properties
private var buttonContainers = [ButtonContainerType: ButtonContainer]()
private var leftContainerWidth: CGFloat {
return buttonContainers[.Accessory]?.containerWidthWhenOpen ?? CGFloat(0)
}
private var rightContainerWidth: CGFloat {
return buttonContainers[.Edit]?.containerWidthWhenOpen ?? CGFloat(0)
}
private var buttonContainerRightAnchor = NSLayoutConstraint()
private var isOpen = false
// MARK: Subviews
private let scrollView = UIScrollView()
// MARK: Lifecycle methods
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
scrollView.delegate = self
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
contentView.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
scrollView.leftAnchor.constraintEqualToAnchor(contentView.leftAnchor).active = true
scrollView.rightAnchor.constraintEqualToAnchor(contentView.rightAnchor).active = true
scrollView.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor).active = true
let scrollContentView = UIView()
scrollContentView.backgroundColor = UIColor.cyanColor()
scrollView.addSubview(scrollContentView)
scrollContentView.translatesAutoresizingMaskIntoConstraints = false
scrollContentView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor).active = true
scrollContentView.leftAnchor.constraintEqualToAnchor(scrollView.leftAnchor).active = true
scrollContentView.rightAnchor.constraintEqualToAnchor(scrollView.rightAnchor).active = true
scrollContentView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor).active = true
scrollContentView.widthAnchor.constraintEqualToAnchor(contentView.widthAnchor, constant: 10).active = true
scrollContentView.heightAnchor.constraintEqualToAnchor(contentView.heightAnchor).active = true
buttonContainers[.Accessory] = ButtonContainer(type: .Accessory, scrollContentView: scrollContentView)
buttonContainers[.Edit] = ButtonContainer(type: .Edit, scrollContentView: scrollContentView)
for bc in buttonContainers.values {
scrollContentView.addSubview(bc)
bc.widthAnchor.constraintEqualToAnchor(contentView.widthAnchor).active = true
bc.heightAnchor.constraintEqualToAnchor(scrollContentView.heightAnchor).active = true
bc.topAnchor.constraintEqualToAnchor(scrollContentView.topAnchor).active = true
bc.containerToContentConstraint.active = true
}
scrollView.contentInset = UIEdgeInsetsMake(0, leftContainerWidth, 0, rightContainerWidth)
}
func closeContainer() {
scrollView.contentOffset.x = CGFloat(0)
}
}
extension SwipeyTableViewCell: UIScrollViewDelegate {
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let xOffset: CGFloat = scrollView.contentOffset.x
isOpen = false
for bc in buttonContainers.values {
if bc.isContainerOpen(xOffset, thresholdVelocity: thresholdVelocity, velocity: velocity) {
targetContentOffset.memory.x = bc.offsetRequiredToOpenContainer()
NSLog("Target offset \(targetContentOffset.memory.x)")
isOpen = true
break /// only one container can be open at a time so cn exit here
}
}
if !isOpen {
NSLog("Closing container")
targetContentOffset.memory.x = CGFloat(0)
let ms: CGFloat = xOffset / velocity.x /// if the scroll isn't on a fast path to zero, animate it closed
if (velocity.x == 0 || ms < 0 || ms > maxClosureDuration) {
NSLog("Animating closed")
dispatch_async(dispatch_get_main_queue()) {
scrollView.setContentOffset(CGPointZero, animated: true)
}
}
}
}
/**
Defines the position of the container view for buttons assosicated with a SwipeyTableViewCell
- Edit: Identifier for a UIView that acts as a container for buttons to the right of the cell
- Accessory: Identifier for a UIView that acts as a container for buttons to the left of the vell
*/
enum ButtonContainerType {
case Edit, Accessory
}
extension ButtonContainerType {
func getConstraints(scrollContentView: UIView, buttonContainer: UIView) -> NSLayoutConstraint {
switch self {
case Edit:
return buttonContainer.leftAnchor.constraintEqualToAnchor(scrollContentView.rightAnchor)
case Accessory:
return buttonContainer.rightAnchor.constraintGreaterThanOrEqualToAnchor(scrollContentView.leftAnchor)
}
}
func containerOpenedTest() -> ((scrollViewOffset: CGFloat, containerFullyOpenWidth: CGFloat, thresholdVelocity: CGFloat, velocity: CGPoint) -> Bool) {
switch self {
case Edit:
return {(scrollViewOffset: CGFloat, containerFullyOpenWidth: CGFloat, thresholdVelocity: CGFloat, velocity: CGPoint) -> Bool in
(scrollViewOffset > containerFullyOpenWidth || (scrollViewOffset > 0 && velocity.x > thresholdVelocity))
}
case Accessory:
return {(scrollViewOffset: CGFloat, containerFullyOpenWidth: CGFloat, thresholdVelocity: CGFloat, velocity: CGPoint) -> Bool in
(scrollViewOffset < -containerFullyOpenWidth || (scrollViewOffset < 0 && velocity.x < -thresholdVelocity))
}
}
}
func transformOffsetForContainerSide(containerWidthWhenOpen: CGFloat) -> CGFloat {
switch self {
case Edit:
return containerWidthWhenOpen
case Accessory:
return -containerWidthWhenOpen
}
}
}
/// A UIView subclass that acts as a container for buttongs associated with a SwipeyTableCellView
class ButtonContainer: UIView {
private let scrollContentView: UIView
private let type: ButtonContainerType
private let maxNumberOfButtons = 3
let buttonWidth = CGFloat(65)
private var buttons = [UIButton]()
var containerWidthWhenOpen: CGFloat {
// return CGFloat(buttons.count) * buttonWidth
return buttonWidth // TODO: Multiple buttons not yet implements - this will cause a bug!!
}
var containerToContentConstraint: NSLayoutConstraint {
return type.getConstraints(scrollContentView, buttonContainer: self)
}
var offsetFromContainer = CGFloat(0) {
didSet {
let delta = abs(oldValue - offsetFromContainer)
containerToContentConstraint.constant = offsetFromContainer
if delta > (containerWidthWhenOpen * 0.5) { /// this number is arbitary - can it be more formal?
animateConstraintWithDuration(0.1, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, completion: nil) /// ensure large changes are animated rather than snapped
}
}
}
// MARK: Initialisers
init(type: ButtonContainerType, scrollContentView: UIView) {
self.type = type
self.scrollContentView = scrollContentView
super.init(frame: CGRectZero)
backgroundColor = UIColor.blueColor()
translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Public methods
func isContainerOpen(scrollViewOffset: CGFloat, thresholdVelocity: CGFloat, velocity: CGPoint) -> Bool {
let closure = type.containerOpenedTest()
return closure(scrollViewOffset: scrollViewOffset, containerFullyOpenWidth: containerWidthWhenOpen, thresholdVelocity: thresholdVelocity, velocity: velocity)
}
func offsetRequiredToOpenContainer() -> CGFloat {
return type.transformOffsetForContainerSide(containerWidthWhenOpen)
}
}
答案 0 :(得分:0)
确定 - 发现错误,这是早期UIScrollView实验中遗留下来的错误。我在之前关于“快照”的评论中提到了线索。发生在所需targetContentOffset的10pt内...
scrollContentView宽度约束设置不正确,如下所示:
scrollContentView.widthAnchor.constraintEqualToAnchor(contentView.widthAnchor, constant: 10).active = true
在我发现我可以通过设置其contentInset强制UIScrollView滚动之前,我只是将子视图放大到UIScrollView被固定到的单元格内容视图。由于我一直在重构代码以使用新的锚属性,旧的代码传播了,我得到了我的错误!
所以,不是iOS有问题......只是我不注意。学习到教训了!我现在有一些关于如何实现可能更整洁的事情的其他想法。