use UIScrollView to show CarouselImagesView - ios

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.

Related

Swift - pageviewcontroller tableview in scrollview using snapkit

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
}
}
}

how to get indexpath of tableView in any method

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
}

How to use pagination in table view

Basically i have a table view and get 5 item in every time when i call the api. So how to use/ reload more data white scrolling table view.
array.count-1 not for me at cell for row indexpath because when device size bigger all data shown at a time and api not call
You should be sending the page number in the API request.
First: declare the variable currentPage with initial value 0 and a boolean to check if any list is being loaded with initial value false, it's to prevent the scroll view from getting more lists/items in one scroll to the bottom of the tableView.
var currentPage = 0
var isLoadingList = false
Second: This is the function that fetches the data:
func getFromServer(_ pageNumber: Int){
self.isloadingList = false
self.table.reloadData()
}
Third: Implement the function that increments the page number and calls the API function on the current page.
func loadMoreItems(){
currentPage += 1
getFromServer(currentPage)
}
Fourth: When the scrollView scrolls you should get the other items/lists from the API.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) && !isLoadingList){
self.isLoadingList = true
self.loadMoreItems()
}
}
You should implement the UITableViewDataSourcePrefetching protocol. This protocol will help you to fill seamlessly a table by new data
Declare
current page = 1
Then add this method in viewController
private func isLastCell(indexPath: IndexPath) -> Bool {
print(indexPath)
if array.count > 0 {
return ((indexPath.section == sectionCount-1) && (indexPath.row == (array.count-1)))
} else {
return false
}
}
After that in tableViewWillDisplayCell method add this function
if array.count > 0, self.isLastCell(indexPath: indexPath) {
self.currentPage += 1
// call api here
}
It works for me

Material "pull to refresh" in Swift?

I am trying to achieve pull to refresh in a WKWebView like in this Material pull to refresh GIF. Because we want to include websites that already have a html navigation bar, we need to keep the web view fix when dragging down. I found pull to refresh (UIRefreshControl) to table views and web views but the views go down as the user drags down.
I set the delegate to scrollView property of the web view and receive notifications. When I drag near to top and get to 0 (vertical scroll view offset) I can disable the scroll view, enable the pan gesture. But to actually move the custom spinner view I need a second touch.
The method func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) doesn't work because I have a scroll view over.
private func gestures() {
self.panGesture = UIPanGestureRecognizer(
target: self,
action: "panGestureCaptured:"
)
self.panGesture!.enabled = false
self.webView.addGestureRecognizer(self.panGesture!)
}
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y <= 0 {
self.view.layoutIfNeeded()
self.spinnerTopLayoutConstraint?.constant = -scrollView.contentOffset.y
}
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0 { // dragging down
if scrollView.contentOffset.y == 0 {
self.webView!.scrollView.scrollEnabled = false
// self.webView!.scrollView.canCancelContentTouches = true
self.panGesture!.enabled = true
// self.refreshWebView()
}
self.showNavigationItems()
} else { // dragging up
self.hideNavigationItems()
}
}
Thanks to #Darko: the idea was to use the panGesture property of the scroll view (also property of the web view).
private func gestures() {
self.webView.scrollView.panGestureRecognizer.addTarget(
self,
action: "panGestureCaptured:"
)
}
func panGestureCaptured(gesture: UIGestureRecognizer) {
let touchPoint = gesture.locationInView(self.webView)
print("touchPoint: \(touchPoint)")
print("panGestureCaptured scrollView offset \(self.webView!.scrollView.contentOffset.y)")
if self.webView!.scrollView.contentOffset.y == 0 {
if self.webView!.scrollView.panGestureRecognizer.state == UIGestureRecognizerState.Changed {
if touchPoint.y < self.webView!.frame.height * 0.3 {
self.spinnerTopLayoutConstraint?.constant = touchPoint.y
} else {
self.spinnerTopLayoutConstraint?.constant = self.webView!.frame.height * 0.3
}
} else if self.webView!.scrollView.panGestureRecognizer.state == UIGestureRecognizerState.Ended {
self.spinnerTopLayoutConstraint?.constant = UIApplication.sharedApplication().statusBarFrame.height + 20
}
}
}
WebView's scroll view already has a pan recognizer (webView.scrollView.panGestureRecognizer) so you could listen to this one (with addTarget) and disable bouncing on the scroll view. In this way you don't need to disable/enable the pan recognizer.

How can I reload UITableView when I scroll to the top with current scroll position? (like chat apps)

I'm working on a chat screen now(UITableView + input TextField).
I want my UITableView reload more chat messages staying current scroll position when I scroll to top(If I load older 20 messages more, I still seeing a 21st message.) KakaoTalk and Line Apps is doing this. In that apps, I can scroll up infinitely because the scroll position is staying.(I mean I'm still seeing the same chat message)
I'm checking the row index in tableView:willDisplayCell:forRowAtIndexPath:, so I fetch more chat messages when the index is 0. I finished the logic fetching more messages from DB, but I didn't finish polishing UI.
This is the code.
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 && !isTableViewLoading {
if photo.chatMessages.count != chatMessages.count {
var loadingChatItems: Array<ChatMessage>
let start = photo.chatMessages.count - chatMessages.count - Metric.ChatMessageFetchAmount
if start > 0 {
loadingChatItems = Database.loadChatMessages(photoId, startIndex: start, amount: Metric.ChatMessageFetchAmount)
} else {
loadingChatItems = Database.loadChatMessages(photoId, startIndex: 0, amount: Metric.ChatMessageFetchAmount + start)
}
chatMessages = loadingChatItems + chatMessages
isTableViewLoading = true
var indexPaths = Array<NSIndexPath>()
for row in 0..<loadingChatItems.count {
let indexPath = NSIndexPath(forRow: row, inSection: 0)
indexPaths.append(indexPath)
}
tableView.scrollEnabled = false
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
} else {
isTableViewLoading = false
tableView.scrollEnabled = true
}
}
Any suggestions?
Swift 3
This post is a bit old, but here a solution:
1) Detect when you are scrolling near to the top in scrollViewDidScroll,
2) Load the new messages
3) Save the contentSize, reload the tableView
4) Set the contentOffset.y of the tableView to (newContentSizeHeight - oldContentSizeHeight) which is exactly the current point
and here the code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == tableView {
if tableView.contentOffset.y < 50 {
loadMessages()
let oldContentSizeHeight = tableView.contentSize.height
tableView.reloadData()
let newContentSizeHeight = tableView.contentSize.height
tableView.contentOffset = CGPoint(x:tableView.contentOffset.x,y:newContentSizeHeight - oldContentSizeHeight)
}
}
}
In viewDidLoad:
self.refreshControl?.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
Then add the function:
func refresh(refreshControl: UIRefreshControl) {
//your code here
refreshControl.endRefreshing()
}
This will allow the user to scroll up to refresh the UITableView.
I have implemented another solution, it uses the scroll listener from the delegate, and the response of the asynchronous service.
So, it scrolls without animation to the position where the loading of more messages started, because messages are being appended to my array (It causes the transition to be barely affected).
I decided to do it in this way because the first solution has a problem, and it is that when you let your row height to be set automatically, then the new content height will not be accurate.
Here's my code.
- (void) scrollViewDidScroll:(UIScrollView*)scrollView {
if (!topReached && scrollView == self.table) {
if (!isLoadingTop && self.table.contentOffset.y < 40) {
isLoadingTop = YES;
[self loadChat];
}
}
}
// method used by the response of the async service
- (void)fillMessages: (NSArray*) messageArray {
if ([messageArray count] == 0) {
topReached = YES;
} else {
if ([messageArray count] < limit) {
topReached = YES;
}
for (int i=0; i<messageArray.count; i++) {
NSDictionary* chat = [messageArray objectAtIndex:i];
[messages insertObject:[[MessageModel alloc] initWithDictionary:chat] atIndex:i];
}
[table reloadData];
if (!isLoadingTop)
[self scrollToBottom];
else {
isLoadingTop = NO;
[self.table
scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[messageArray count] inSection:0]
atScrollPosition:UITableViewScrollPositionTop animated:false];
}
offset += limit;
}
}
As an adicional information, I like to block the access of my async services consumptions in a view controller, so one service can be called just once in the same view.

Resources