I'm trying to show animation if the indexPath == 0 and scroll direction is downward, but I'm unable to get the indexPath of tableView in scrollViewdidScroll method. here's my code -->
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let indexPath = tableView.indexPath(for: ThirdTableViewCell() as UITableViewCell)
if (self.lastContentOffset > scrollView.contentOffset.y) && indexPath?.row == 0 {
print ("up")
}
else if (self.lastContentOffset < scrollView.contentOffset.y) {
// print ("down")
}
self.lastContentOffset = scrollView.contentOffset.y
}
I got the indexPath nil while debugging
You still haven't explained what you really want to do, but maybe this will help...
Start by setting up your scrollViewDidScroll like this:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var partOfRowZeroIsVisible: Bool = false
var topOfRowZeroIsVisible: Bool = false
var userIsDraggingDown: Bool = false
// if user pulls table DOWN when Row Zero is at top,
// contentOffset.y will be negative and table will
// bounce back up...
// so, we make y AT LEAST 0.0
let y = max(0.0, tableView.contentOffset.y)
// point for the top visible content
let pt: CGPoint = CGPoint(x: 0, y: y)
if let idx = tableView.indexPathForRow(at: pt), idx.row == 0 {
// At least PART of Row Zero is visible...
partOfRowZeroIsVisible = true
} else {
// Row Zero is NOT visible...
partOfRowZeroIsVisible = false
}
if y == 0 {
// Table view is scrolled all the way to the top...
topOfRowZeroIsVisible = true
} else {
// Table view is NOT scrolled all the way to the top...
topOfRowZeroIsVisible = false
}
if (self.lastContentOffset >= y) {
// User is DRAGGING the table DOWN
userIsDraggingDown = true
} else {
// User is DRAGGING the table UP
userIsDraggingDown = false
}
// save current offset
self.lastContentOffset = y
Then, to show or hide your "arrow":
// if you want to show your "arrow"
// when ANY PART of Row Zero is visible
if partOfRowZeroIsVisible {
arrow.isHidden = false
} else {
arrow.isHidden = true
}
or:
// if you want to show your "arrow"
// ONLY when table view has been scrolled ALL THE WAY to the top
if topOfRowZeroIsVisible {
arrow.isHidden = false
} else {
arrow.isHidden = true
}
or:
// if you want to show your "arrow"
// when ANY PART of Row Zero is visible
// and
// ONLY if the user is dragging DOWN
if partOfRowZeroIsVisible && userIsDraggingDown {
arrow.isHidden = false
} else {
arrow.isHidden = true
}
or:
// if you want to show your "arrow"
// ONLY when table view has been scrolled ALL THE WAY to the top
// and
// ONLY if the user is dragging DOWN
if topOfRowZeroIsVisible && userIsDraggingDown {
arrow.isHidden = false
} else {
arrow.isHidden = true
}
Related
I just faced a really difficult point while making a complicated view that has pageViewController, tableview, and scrollview.
This is what I want to program
case 1) scroll up : button stactview and pageviewcontroller locate headerview.snp.botttom and hide red view and tableview will scroll
case 2) scroll down : show red view, button stactview and pageviewcontroller locate red view.snp.bottom (like photo)
I made it like these cases, but the problem is tableview in pageviewcontroller didn't move.
What should I do to make it smooth scrolling?
I found example what I want to make
https://raw.githubusercontent.com/tsucres/HeaderedTabScrollView/master/Screenshots/customAnim.gif
http://cocoadocs.org/docsets/MXSegmentedPager/3.2.2/
I build view programmatically using snapkit
self.tabTopValue = 440 // redview height
self.view.addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.top.equalTo(headerView.snp.bottom)
}
scrollView.addSubview(contentView)
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalTo(self.scrollView)
}
redView.snp.makeConstraints { make in
make.leading.equalTo(contentView.snp.leading)
make.trailing.equalTo(contentView.snp.trailing)
make.top.equalTo(header.snp.bottom).offset(0)
make.height.equalTo(440)
}
// tabbar
tabButtonStackView.addArrangedSubview(historyInfoButton)
tabButtonStackView.addArrangedSubview(graphButton)
tabButtonStackView.addArrangedSubview(lineageInfoButton)
contentView.addSubview(tabButtonStackView)
tabButtonStackView.snp.makeConstraints { make in
make.leading.trailing.equalTo(contentView)
tabTopConstraint = make.top.equalTo(headerView.snp.bottom).offset(self.tabTopValue).constraint
make.height.equalTo(40)
}
addChild(pageViewController)
contentView.addSubview(pageViewController.view)
pageViewController.didMove(toParent: self)
pageViewController.view.snp.makeConstraints { make in
make.top.equalTo(tabButtonStackView.snp.bottom)
make.leading.trailing.equalTo(contentView)
make.height.equalTo(scrollView)
make.bottom.equalTo(contentView.snp.bottom).offset(0)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("detail scroll = \(scrollView.contentOffset.y)")
if scrollView.contentOffset.y < 0 {
self.tabButtonStackView.snp.updateConstraints { make in
self.tabTopConstraint = make.top.equalTo(headerView.snp.bottom).offset(scrollView.contentOffset.y + tabTopValue).constraint
}
} else if scrollView.contentOffset.y == 0 {
UIView.animate(withDuration: 0.3, delay: 0.0, animations: {
self.tabButtonStackView.snp.updateConstraints { make in
self.tabTopConstraint = make.top.equalTo(self.headerView.snp.bottom).offset(self.tabTopValue).constraint
}
}, completion: nil)
}
if scrollView.contentOffset.y < tabTopValue {
self.tabButtonStackView.snp.updateConstraints { make in
self.tabTopConstraint = make.top.equalTo(headerView.snp.bottom).offset(tabTopValue - scrollView.contentOffset.y).constraint
}
} else if scrollView.contentOffset.y > tabTopValue {
self.tabButtonStackView.snp.updateConstraints { make in
self.tabTopConstraint = make.top.equalTo(headerView.snp.bottom).offset(0).constraint
}
}
}
I have a UIScrollView can show a set of pages and have "carousel" effect, what I want is snap to an image after the user finishes dragging.Should I add some other func such like scrollViewDidEndDragging to observe user's action??? Thank you.
Code
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
timer?.invalidate()
scrolled(scrollView: scrollView)
}
private func scrolled(scrollView: UIScrollView) {
guard let imageUrls = self.imageUrls else {
return
}
if imageUrls.count < 1 {
return
}
.
if abs(lastContentOffset - scrollView.contentOffset.x) < 50 {
return
}
if lastContentOffset > scrollView.contentOffset.x {
if currentPageIndex == 0 {
currentPageIndex = imageUrls.count - 1
} else {
currentPageIndex -= 1
}
} else {
if currentPageIndex < imageUrls.count - 1 {
currentPageIndex += 1
} else {
currentPageIndex = 0
}
}
layoutImages()
self.delegate?.didChangePage(currentPageIndex)
}
First of all scroll view are not reliable from memory point of view, in future if there are lots of images loaded dynamically your app may end up in memory flood situation and may crash.
Better use views like collection view, stack view or table view for cell reusability and it will also solve dragging problem.
I have two UICollectionVews
One of them (The parent one) is a full-screen-cell paginated collection view.
The other one (The child one) is a filter inside the "page"
Both have the same scroll direction
My problem is that when I'm scrolling the child one, and it reaches the end, the parent one starts moving. And I would like to avoid that. I tried many things
*ScrollView delegates
*Touchesbegan
Any ideas?
Thanks!
I think it's easy .
To set the parent UICollectionView
collectionView.scrollEnabled = NO;
Or it sounds unreasonable . If the parent UICollectionView could scroll, how could you archive your goal? Because UICollectionView is consisted of the cells (The child one). The cell will affect the parent UICollectionView inevitably.
Maybe The child one is located in part of the cell , you could use the
UIScrollViewDelegate's method: scrollViewDidScroll , to set the parent collectionView's scrollEnabled property.
I think you prefer this answer.
As we know a pan gesture recognizer is built in UIScrollView.
We could add an other coordinated pan gesture.
Because the apple says: 'UIScrollView's built-in pan gesture recognizer must have its
scroll view as its delegate.'
let pan = UIPanGestureRecognizer(target: self, action: #selector(ViewController.panGestureRecognizerAction(recognizer:)))
pan.delegate = self
mainScrollView.addGestureRecognizer(pan)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
// So we can get the argus of the pan gesture while not affecting the scroll
after the setting.
var mainScrollEnabled = false
var subScrollEnabled = false
// Then we define two BOOL values to identify the scroll of the collectionView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
if scrollView.contentOffset.y >= maxOffsetY {
scrollView.setContentOffset(CGPoint(x: 0, y: maxOffsetY), animated: false)
mainScrollView.isScrollEnabled = false
subScrollView.isScrollEnabled = true
subScrollEnabled = true
mainScrollEnabled = false
}
}else {
if scrollView.contentOffset.y <= 0 {
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
subScrollView.isScrollEnabled = false
mainScrollView.isScrollEnabled = true
mainScrollEnabled = true
subScrollEnabled = false
}
}
}
// Then we handle the situation that the collectionView reaches the end , by the pan gesture's recognizer .
var currentPanY: CGFloat = 0
func panGestureRecognizerAction(recognizer: UIPanGestureRecognizer) {
if recognizer.state != .changed{
currentPanY = 0
// clear the data of last time after finishing the scroll
mainScrollEnabled = false
subScrollEnabled = false
}else {
let currentY = recognizer.translation(in: mainScrollView).y
// So the collectionView reaches the end.
if mainScrollEnabled || subScrollEnabled {
if currentPanY == 0 {
currentPanY = currentY //get the y
}
let offsetY = currentPanY - currentY //get the offsetY
if mainScrollEnabled {
let supposeY = maxOffsetY + offsetY
if supposeY >= 0 {
mainScrollView.contentOffset = CGPoint(x: 0, y: supposeY)
}else {
mainScrollView.contentOffset = CGPoint.zero
}
}else {
subScrollView.contentOffset = CGPoint(x: 0, y: offsetY)
}
}
}
}
I'm trying to replicate the scroll selector at the bottom of the emoji keyboard, where you can select the category by tapping on the appropriate icon and it will also show you in which category you are if you scroll. However, I'm having trouble getting the selected button to appear highlighted for some reason. I can get the scroll to work fine, but when I click on the icon it won't stay highlighted. Is this to do with some property of the touchUpInside trigger and the highlighted property?
My Code
var categoryButtons = [UIButton]()
var categoryDistances = [CGFloat]()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var curIndex = -1
for d in categoryDistances {
if (scrollView.contentOffset.x + 5 >= d) {
curIndex += 1
}
}
for b in categoryButtons {
b.isHighlighted = false
}
if (curIndex == -1) {
curIndex = 0
}
categoryButtons[curIndex].isHighlighted = true
}
func shortcutSelected(_ sender: UIButton) {
snapSelector.contentOffset.x = categoryDistances[categoryButtons.index(of: sender)!]
for b in categoryButtons {
b.isHighlighted = false
}
sender.isHighlighted = true
}
Got it to work the way I wanted by applying a tint color instead of using the isHighlighted or isSelected properties:
let tintedImg = origImg?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
let shortcut = UIButton()
shortcut.setBackgroundImage(tintedImg, for: .normal)
shortcut.tintColor = UIColor.gray
i'm making an messenger app, like telegram, i would like to implement an infinite scroll, i use JSQMessageViewController for the chat.
this is the code for infinite scroll:
override func scrollViewDidScroll(scrollView: UIScrollView) {
if(scrollView.contentOffset.y >= 0.0 && scrollView.contentOffset.y <= 30.0 && loading == true){
self.loading = false
self.loadMore()
}
}
Then i call this function:
func loadMore() {
//Controllo se nell'array sono presenti altri messaggi
if ((chatList?.messages.count)!-1) != self.messages.count{
//
CATransaction.begin()
CATransaction.setDisableActions(true)
let oldBottomOffset = self.collectionView!.contentSize.height - self.collectionView!.contentOffset.y
UIView.performWithoutAnimation({ () -> Void in
self.collectionView!.performBatchUpdates({ () -> Void in
let idInit = (self.lastMessageId-self.messagePerPage<0) ? 0 : self.lastMessageId-1-self.messagePerPage
let idEnd = self.lastMessageId-1
var indexPaths: [NSIndexPath] = []
for(var i = 0; i<idEnd-idInit; i++){
print("Creo indexpath \(i)")
indexPaths.append(NSIndexPath(forItem: i, inSection: 0))
}
self.collectionView!.insertItemsAtIndexPaths(indexPaths)
let msg = self.chatList?.messages
var i = idEnd
repeat {
let mg = msg![i]
let messagechat:JSQMessage = JSQMessage(senderId: mg.sender, senderDisplayName: "", date:mg.time, text: mg.message)
messagechat.xmppMessageID = mg.messageID
messagechat.status = mg.messageStatus
self.messages.insert(messagechat, atIndex: 0)
self.lastMessageId = i
i--
} while i > idInit
// invalidate layout
self.collectionView!.collectionViewLayout.invalidateLayoutWithContext(JSQMessagesCollectionViewFlowLayoutInvalidationContext())
}, completion: {(finished) in
//scroll back to current position
self.finishReceivingMessageAnimated(false)
self.collectionView!.layoutIfNeeded()
self.collectionView!.contentOffset = CGPointMake(0, self.collectionView!.contentSize.height - oldBottomOffset)
CATransaction.commit()
})
})
}
else {
print("No more messages to load.")
}
}
All work fine but while scrolling the scroll is stopped for 1sec. i suppose is CATransaction that make this and sometimes i see a jump effect, if i add 10 new messages i see for a second the first message of the 10, and then offset is set correctly to the older position.
How can i fix this? i see telegram scrolling is perfect, and there is no jump effects while loading old messages.