我在 swift 中拼凑了一个 UITableView 子类,它复制了 Apple 自己的应用程序中使用的可滑动按钮的一些功能。它在 Xcode 7.0 beta 4 中运行良好,但由于使用 beta 5,当表格首次显示时,单元格最初显示的 contentOffset 不正确(等于用于隐藏按钮的 contentInset)。旋转视图或将单元格滚动到屏幕外可以更正该问题 - 此错误仅在表格视图的初始呈现时发生。
在创建单元格时检查它们的 contentOffset 表明 contentOffset 设置为零,但在生命周期的某个地方,此值被覆盖。我可以通过将以下内容添加到表视图控制器来解决问题:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
for cell in table.visibleCells {
(cell as! SwipeableTableViewCell).scrollView.contentOffset.x = CGFloat(0)
}
}
但是,它不是很优雅,因为我显然是在与系统作斗争。谁能解释自 Beta 4 以来发生了什么变化?
下面 UITableViewCell 子类的源代码(对于这个早期实现中的代码风格表示歉意 - 我是新手,仍在重构过程中)
class SwipeableTableViewCell: UITableViewCell, UITableViewDelegate {
//MARK: Constants
private let thresholdVelocity = CGFloat(0.6)
private let maxClosureDuration = CGFloat(40)
private let buttonCount = 3
private let buttonWidth = CGFloat(50)
//MARK: Computed properties
private var buttonContainerWidth: CGFloat {
return CGFloat(buttonCount) * buttonWidth
}
//MARK: Properties
let scrollView = UIScrollView() // TODO: was originally private but have had to allow superclass access to correct contentOffset glitch
private let scrollContentView = UIView() /// positioned using constraints
private var buttonContainers: (left: ButtonContainer!, right: ButtonContainer!)
private let labelView = UILabel() /// positioned using constraints
private var buttonsLeft = [SwipeableCellButton]()
private var buttonsRight = [SwipeableCellButton]()
private var viewDictionary: [String: UIView]!
private let buttonColors = [UIColor.redColor(), UIColor.orangeColor(), UIColor.purpleColor()]
//MARK: Initialisers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
viewDictionary = ["cv": contentView, "sv" : scrollView, "scv" : scrollContentView, "lv" : labelView]
}
//MARK: Lifecycle methods
override func awakeFromNib() {
super.awakeFromNib()
// setup scrollView display characteristics
scrollView.delegate = self
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
// build cell's view hierachy
contentView.addSubview(scrollView)
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[sv]|", options: [], metrics: nil, views: viewDictionary))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[sv]|", options: [], metrics: nil, views: viewDictionary))
// setup scrollContentView
scrollContentView.translatesAutoresizingMaskIntoConstraints = false
scrollContentView.backgroundColor = UIColor.greenColor()
scrollView.addSubview(scrollContentView)
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[scv(==cv)]", options: [], metrics: nil, views: viewDictionary)) // match width of cell contentView
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[scv(==cv)]", options: [], metrics: nil, views: viewDictionary)) // match height of cell contentView
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scv]|", options: [], metrics: nil, views: viewDictionary)) // pin L+R edges to scrollview
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scv]|", options: [], metrics: nil, views: viewDictionary)) // pin top+bottom edges to scrollview
labelView.backgroundColor = UIColor.blueColor()
labelView.textColor = UIColor.whiteColor()
labelView.text = "TEST LABEL VIEW"
labelView.translatesAutoresizingMaskIntoConstraints = false
scrollContentView.addSubview(labelView)
scrollContentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[lv]|", options: [], metrics: nil, views: viewDictionary)) /// pin to L&R edges of superview
scrollContentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[lv]|", options: [], metrics: nil, views: viewDictionary)) /// pin to top and bottom of superview
// setup button containers
buttonContainers.left = ButtonContainer(properties: ContainerProperties.Left(width: buttonContainerWidth, height: contentView.frame.height, buttonWidth: buttonWidth))
buttonContainers.right = ButtonContainer(properties: ContainerProperties.Right(width: buttonContainerWidth, height: contentView.frame.height, buttonWidth: buttonWidth))
buttonContainers.right.frame.origin.x = contentView.frame.width
viewDictionary.updateValue(buttonContainers.left, forKey: "bcl")
viewDictionary.updateValue(buttonContainers.right, forKey: "bcr")
for i in 1...buttonCount {
var endX = buttonContainerWidth - (CGFloat(i) * buttonWidth) // calc for left button container
let buttonL = SwipeableCellButton(container: buttonContainers.left, width: buttonWidth, endX: endX)
buttonL.setAppearance("BUT\(i)", titleColor: UIColor.whiteColor(), backgroundColor: buttonColors[i - 1])
endX = CGFloat(i - 1) * buttonWidth // re-calc for right button container
let buttonR = SwipeableCellButton(container: buttonContainers.right, width: buttonWidth, endX: endX)
buttonR.setAppearance("BUT\(i)", titleColor: UIColor.whiteColor(), backgroundColor: buttonColors[i - 1])
buttonsLeft.append(buttonL)
buttonsRight.append(buttonR)
buttonContainers.left.addSubview(buttonL)
buttonContainers.right.addSubview(buttonR)
}
scrollContentView.addSubview(buttonContainers.left)
scrollContentView.addSubview(buttonContainers.right)
// enable scrolling by adding an inset
scrollView.contentInset = UIEdgeInsetsMake(0, buttonContainers.left.frame.width, 0, buttonContainers.right.frame.width)
}
override func layoutSubviews() {
super.layoutSubviews()
/// ensure x origin of R button container frmme is set correcntly
buttonContainers.right.frame.origin.x = contentView.frame.width
/// reset offset
scrollView.contentOffset = CGPointZero
NSLog("x offset after layoutSubviews: \(scrollView.contentOffset.x)")
}
//MARK: Private methods
private func updateButtonPositions(buttonContainerVisibleWidth: CGFloat) {
for s in buttonContainers.left.subviews {
if let b = s as? SwipeableCellButton {
b.updatePosition(buttonContainerVisibleWidth)
}
}
for s in buttonContainers.right.subviews {
if let b = s as? SwipeableCellButton {
b.updatePosition(buttonContainerVisibleWidth)
}
}
}
}
//MARK: Extension - UIScrollViewDelegate
extension SwipeableTableViewCell : UIScrollViewDelegate {
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
NSLog("Will begin dragging")
}
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
NSLog("viewWillEndDragging")
let x: CGFloat = scrollView.contentOffset.x
NSLog("X offset \(x)")
let left = buttonContainers.left.frame.width, right = buttonContainers.right.frame.width
if (left > 0 && (x < -left || (x < 0 && velocity.x < -thresholdVelocity))) {
targetContentOffset.memory.x = -left
} else if (right > 0 && (x > right || (x > 0 && velocity.x > thresholdVelocity))) {
targetContentOffset.memory.x = right
} else {
targetContentOffset.memory = CGPointZero
// if the scroll isn't on a fast path to zero, animate it closed
let ms: CGFloat = x / velocity.x
if (velocity.x == 0 || ms < 0 || ms > maxClosureDuration) {
dispatch_async(dispatch_get_main_queue()) {
scrollView.setContentOffset(CGPointZero, animated: true)
}
}
}
}
func scrollViewDidScroll(scrollView: UIScrollView) {
NSLog("viewDidScroll x offset \(scrollView.contentOffset.x)")
let buttonContainerVisibleWidth = min(buttonContainerWidth, abs(scrollView.contentOffset.x))
updateButtonPositions(buttonContainerVisibleWidth)
/// stop the left button container moving away from the left margin of the cell
if (scrollView.contentOffset.x < -buttonContainers.left.frame.width) {
// Make the left buttonsLeft stay in place.
buttonContainers.left.frame = CGRectMake(scrollView.contentOffset.x, 0, buttonContainers.left.frame.width, buttonContainers.left.frame.size.height)
/// prevent right button container moving away from the right margin of the cell
} else if (scrollView.contentOffset.x > buttonContainers.right.frame.width) {
buttonContainers.right.frame = CGRectMake(scrollView.frame.size.width - buttonContainerWidth + scrollView.contentOffset.x, 0, buttonContainers.right.frame.width, buttonContainers.right.frame.height)
}
}
}