Search code examples
iosuitableviewswiftcontentoffset

UITableView get middle position via content offset


I want to add a little button in the bottom right corner of a UITableView. When we are in the top half of the tableview, the button programmatically scrolls you to the bottom, and when you are in the bottom half, it takes you to the top. And the button's icon changes from "goto top" to "goto bottom" or vice versa depending on the situation.

In my code below, the button works fine- you press it when you are at the top, you hit the bottom, and the button graphic changes to the "goto top" icon. However, dragging up and down like you would traditionally in a table doesn't make the button change it's icon properly. You even need to pull past the border (top or bottom) of the table to make it flip to the correct state.

When a button is pressed, if we are past halfway (as defined by a function pastHalfway), we go either to the top or bottom of the table. Something is wrong with it, but I've tussled a bit and basically made things not work well in various ways. I think the problem is I am not determining the midpoint content offset of the table correctly.

func pastHalfway() -> Bool {
    // TODO: This doesn't work
    let offset:CGPoint = self.tableView.contentOffset
    let height:CGFloat = self.tableView.frame.height
    println("pastHalfway offset.y=\(offset.y) height=\(height)")
    return offset.y > (height/3.0) // 3.0 is 3/4 down the table
}


func gotoButtonPressed(sender: UIButton!) {
    if let realPost = post {
        if realPost.numberComments > 0 {
            if self.pastHalfway() {
                let indexPath:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
                self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Top, animated: true)
            } else {
                let indexPath:NSIndexPath = NSIndexPath(forRow: realPost.numberComments - 1, inSection: 1)
                self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
            }
        }
    }
}

func maybeShowGotoButtons() {
    let yOffset:CGFloat = self.tableView.contentOffset.y

    if (self.post!.numberComments < minRowsToShowGotoButtons) {
        // Hide and disable buttons
        self.gotoButton.hidden = true
        self.gotoButton.enabled = false
    } else {
        // Show buttons, depending on content offset

        if self.pastHalfway() {
            self.gotoButton.setImage(UIImage(named: "gotoTopIcon"), forState: .Normal)
        } else {
            self.gotoButton.setImage(UIImage(named: "gotoBottomIcon"), forState: .Normal)
        }

        // And enable
        self.gotoButton.hidden = false
        self.gotoButton.enabled = true
    }
}

UIScrollView Delegates

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    UIView.animateWithDuration(0.1, animations: {
        self.gotoButton.alpha = 0.5
    })
}

override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    UIView.animateWithDuration(0.2, animations: {
        self.gotoButton.alpha = 1.0
        self.maybeShowGotoButtons()
    })
}

override func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
    // This is used for the programmatic scroll top/bottom when clicking buttons
    self.maybeShowGotoButtons()
}

Solution

  • The necessary fixes were adding 1)

    override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        self.maybeShowGotoButtons()
    }
    

    2)

    func atBottom() -> Bool {
        let height = self.tableView.contentSize.height - self.tableView.frame.size.height
    
        if self.tableView.contentOffset.y < 10 {
            //reach top
            return false
        }
        else if self.tableView.contentOffset.y < height/2.0 {
            //not top and not bottom
            return false
        }
        else {
            return true
        }
    }