我已經在UITableViewCell中實現了一個UIScrollView,使用戶能夠以與iOS郵件應用程序相同的方式向左和向右滾動顯示按鈕。明確設置框架和位置的原始實現工作得很好,但我重構了代碼以在整個過程中使用自動佈局。用於隱藏/顯示左側按鈕(配件按鈕)的'容器'的動畫效果很好,但是當正確的容器(編輯按鈕)在達到所需的偏移量之前放慢其速度之前,使其滾動視圖停止的動畫會停止位置。UIScrollView動畫對象ContentContentOffset不穩定
根據容器所在的側面和記錄顯示的值是否正確,所有計算都使用剛剛變換的相同數學(例如+而不是 - value,>而不是測試中的<)。我看不到任何明顯的代碼錯誤,並且在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)
}
}
更新 - 僅當contentOffset大於等於10pt或大於targetContentOffset時,纔會顯示'snap'到最終偏移量(而非平滑減速度)。 10點以下的動畫是光滑的。我會繼續狩獵,看看是否有某些虛假的附加信息在某處發生,但看起來這可能是iOS中的一個錯誤 – rustproofFish