2015-09-30 38 views
0

我已經在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) 
    } 
} 
+0

更新 - 僅當contentOffset大於等於10pt或大於targetContentOffset時,纔會顯示'snap'到最終偏移量(而非平滑減速度)。 10點以下的動畫是光滑的。我會繼續狩獵,看看是否有某些虛假的附加信息在某處發生,但看起來這可能是iOS中的一個錯誤 – rustproofFish

回答

0

確定 - 找到了錯誤,並且這是之前UIScrollView實驗留下的錯字。線索是在我前面關於「捕捉」所需targetContentOffset的10PT內發生的評論...

的scrollContentView寬度約束設置不正確如下:

scrollContentView.widthAnchor.constraintEqualToAnchor(contentView.widthAnchor, constant: 10).active = true 

之前,我發現我能強制UIScrollView通過設置其contentInset進行滾動,我只是將子視圖放大到UIScrollView所固定的單元格的contentView。由於我一直在重構代碼逐字使用新的錨定屬性,舊的代碼傳播,我得到了我的錯誤!

所以,不是iOS的錯......只是我沒有注意。學習到教訓了!我現在還有一些關於如何實現可能會更整潔一些的其他想法。