How to add a button to the end of UIScrollView to load more? - ios

I have a scroll with paging. I want to load only 75 pages at a time and when the user reach the last page a button showes up with the last swipe. The button has to be clicked to load more.
The same like ios Zillow app as you see in the attached image.
How should I add this button? Is it part of the scroll or is it just a button out side the scroll?

So let's first think about how we initialize our table view. We use
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
to return the number of cells we want in the table, so let's say we use something like
return searchResultsArray.count
but if we want a "Load More Results" button, then let's add 1 more cell
return searchResultsArray.count + 1
Okay great, now how do we add the button in this new cell? We want to use
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
and we'll say
if indexPath.row == searchResultsArray.count
(remember that an array's count value is 1 greater than the index of the last element in the array, so this if statement is basically saying, "if this cell is the load more cell")
then we will create our button in this cell, and we can link a function to the button (using a selector) which will add results to the searchResultsArray, and then reload the table (using tableView.reloadData())
This is how you would approach this if it were a (vertical scrolling) table view, but if you want to recreate the horizontal scrolling interface in zillow, then you need to use a collection view, which is a bit complicated if you're new to iOS development, but still use the basic concept I described above. Here's a great video to help you with that: https://www.youtube.com/watch?v=Ko9oNhlTwH0

I will recommend you to go for UICollectionView rather than UIScrollView as with UIScrollView you will create multiple objects for your UIImageView and ad it in your UIScrollView which will take more memory as compare to UICollectionView, as with UICollectionView you can reuse the same cell so the memory consumed by UICollectionView will be very less as compare to UIScrollView.
Regrading adding button at the end, create two custom cell in your UICollectionView one is to show UIImageView and one is to show Load More button, fetch 75 results at a time and in the datasource numberOfItemsInSection return 75+1 (i.e. your array count + 1 [+1 to show load more button when you want to show it])
Now in cellForItemAtIndexPath compare if the indexpath.row count is greater than your array count then load the cell for Load More button.

Related

How to left/right-align custom UITableView cell

I’m coding a “chatbot” app, and to hold the message bubbles I have a UITableView and a custom message-bubble shaped cell. Here’s what it looks like so far:
All the cells will look the same, except I’d like every other cell to be, say, half the width of the table and alternating right/left aligned. How could I do this programmatically?
The better way - to create two classes InMessageCell, OutMessageCell, and add all properties (like aligning of all elements) hide inside of this cell. Both cell will have the same width, but all bubbles will be moved on one or other side. It may inheritance from the main class MessageCell, so all logic may stay in main class, but UI part - splitted up.
Two straightforward ways of achieving this by using custom table view cells:
Have two different cell classes. They can share much of their implementation (e.g. by class heritage), but differ in their layout (one is left aligned, one right aligned). For each row in your table, decide which cell you need to use, and dequeue the appropriate one.
Have one cell class whose layout can be toggled when it's being dequeued. (How exactly this toggle looks like depends of course on how you chose to layout your cell. It could e.g. be that you exchange the constant to an autolayout constraint you reference via #IBOutlet, the switching of a background image, or changing the alignment property of a stack view, among many others.)
Both ways depend on making a decision which cell flavor to use in your UITableViewDataSource's tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) function.
Here is a slightly abstract example using the first method. In your UITableViewDataSource:
enum ChatCellAlignment {
case left
case right
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellAlignment: ChatCellAlignment = self.cellAlignmentForIndexPath(indexPath) // you need to implement something like this
var identifier = "Default"
switch cellAlignment {
case .left:
identifier = "LeftAlignedBubbleCell"
case .right:
identifier = "RightAlignedBubbleCell"
}
let cell = self.tableView.dequeueReusableCell(withIdentifier: identifier)
if let cell = cell as? ChatBubbleCell { // assuming your custom cell classes all inherit from a "ChatBubbleCell"
cell.set(text: self.textForIndexPath(indexPath))
... // whatever setup you need to do on your custom cell
}
return cell
}
You can give the table view cell a value to know it. Then you can use autolayout (SnapKit) to make it align left or right

Does indexpath.row take care of iterating an array?

I'm working through a UITableView tutorial and I've become curious about array iteration as I implement the UITableViewDataSource methods. When we call indexPath.row, is this calling a for loop for us behind the scenes? I'm asking because months back when I was learning to use data from a webservice (I've forgotten the exact steps of how I did it precisely) but I believe I needed to iterate over the array in order to present the information in the console.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an instance of UITableViewCell, with default appearance
let cell = UITableViewCell.init(style: .value1, reuseIdentifier: "UITableViewCell")
// Set the text on the cell with the description of the item instance that is at the nth index of the allItems array, where n = row this cell will appear in on the tableview
let item = itemStore.allItems[indexPath.row]
cell.textLabel?.text = item.name
cell.detailTextLabel?.text = "$\(item.valueInDollars)"
return cell
}
Am I correct in thinking that the indexPath.row method is iterating over the array for us behind the scenes?
let item = itemStore.allItems[indexPath.row]
No, calling indexPath.row does not iterate through all rows for you. It's just a variable sent to cellForRowAt that you can access and use to create your cell. It contains information about which row in which section the function cellForRowAt was called for.
However, cellForRowAt is actually called every time a cell is going to be visible on your tableVIew. Imagine you have a dataSource with 100 items and it is possible to only see 10 cells at a time. When the tableView gets initially loaded, cellForRowAt will get called 10 times. Then, if you scroll your tableView to show 3 more cells, cellForRowAt will get called 3 more times.
First of all the tutorial seems to be pretty bad. Table view cells are supposed to be reused
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
The workflow to display table view cells is:
The framework calls numberOfRowsInSection (and also numberOfSections)
For each section/row pair in the range of the visible cells it calls cellForRowAt and passes the index path in the second parameter.
No. The indexPath is just a struct with a section and a row. You use those to directly look up the information from your array. No iteration is happening.
cellForRowAt is only called for the cells that are on screen, so the order they are called depends on which direction you are scrolling.
In fact, the UITableView doesn't even know if you are using an array, or generating the information on the fly. What you do with the indexPath row and section is up to you.
Check the documentation of UITableView and cellForRowAtIndexpath functions.
An index path locating a row in tableView.
IndexPath will be a structure, which helps you to get the specific location from a nested array. In your case of the table view, rows inside the section.
cellForRow will return a cell for particular index path(in the specified section and row)
So indexPath.section will give the section number for which the cell is going to be modified and returned to the data source. And Inside the section, and indexPath.row will be the corresponding row inside that section.
index path documentation
tableView-CellForRowAt: IndexPath
UITableView DataSource Documentation
[update]
If you want to add multiple sections, add another dataSource numberOfSections, and return the number of sections count from it.
I can link to a create a table view tutorial from Apple, but it's a long document, you can skip the starting and read the To display a section in your table view.
If you want, you can use a 2D array for keeping sections and rows. And in numberOfSections method return array.count, and in numberOfRows:inSection, return array[section].count
More examples,
example
example
thanks,
J

How to add a cell with two buttons

I am trying to have two buttons as the first row and the rest of the rows as it is shown in the following picture (I apologize for the flipped picture):
I know how to put two buttons in a cell, like so:
And use the following code to add the first cell to my tableview like so:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
//Configure the first cell with the two buttons
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = cathyList[indexPath.row]
}
return cell
}
My logic allows me to have two buttons as the first row cell but I don't know how to make the buttons big and square like the ones that are shown here. Any suggestions?
You format your button as what you want on Storyboard. I suggest 2 options you can get this:
You can add image and title on button and use Edge property to achieve this. Look at this:
This way is hard to achieve what you need, you have to make sure your icon has the right size, and it's not dynamic.
The second way is easier and more dynamic. But maybe not the best solution. I usually use this way.
You add an image (show your icon), a label (your title) and embed them in a view. Then add a button stretch all the view. Connect this button to action and use it as usual.
Hope this help.

Swift UI collection view layout

I am trying to use a UIcollectionView in my SpriteKit game to display the level select scene. The collection view has 6 sections (start, world 1-5) with 8 cells in each (level 1-8). They are custom cells with a simple UILabel for now.
I can use the did select item for index path to load my levels, it's all good. However my problem is the following
The first section in the collection view is the start screen so I don't want the collection view to show the 8 cells in that section. That section is supposed o only showing a background image and a tap to start lable. So what I tried in cellForIndexPath is to
1) hide cells in that section but that causes text label issues with the cells in the other sections
2) hide the text labels and make the cells color transparent, same problem as 1
So basically what could I do to solve this issue?
I could put the start section into a different SKScene but I prefer if it's all in the collection view.
Another option I was thinking is to make each section have only 1 cell, the size of screen, and add 8 UIButtons to each section except the start section.
I also need some of those cells/buttons to be disabled until the previous level is unlocked. I am not sure what a better approach is, 8 cells as buttons or 8 UIButtons in 1 cell.
I am only looking for a UICollectionView solution, I already have an alternative, because it will make my life much easier when converting to tvOS and using the focus stuff and navigating through 6 sections with 30+ buttons
Thank you very much for any help
You can register more that one kind of cell in UICollectionView:
If you are using Interface builder add another Collection View Cell from Objects library and set it's reuse identifier
Otherwise register nib or class in viewDidLoad method of your UICollectionViewController using self.collectionView?.registerNib(forCellWithReuseIdentifier:) or self.collectionView?.registerClass(forCellWithReuseIdentifier:)
Then when you dequeue cell use proper reuse identifier:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var reuseIdentifier = "identifier of cell with 8 button"
if indexPath.section == 0 {
reuseIdentifier = "identifier of start cell as you set in IB of viewDidLoad"
}
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
// Configure the cell depending on indexPath
return cell
}
I'd go for 8 cells as buttons rather than using 1 cell with a series of UIButtons, that will keep things simple and allow you to use didSelectItemAtIndexPath. Can't you just use numberOfItemsInSection to define only 1 cell in the first section?
My approach would probably be to use a custom flow layout and a supplementary view for your background image and tap to start button - a bit complex but very flexible.

How to build swipe view controller in swift

I have a UITableViewController and when I click on it, it will show a DetailViewUIController.
I want to add a functionality which when I am in DetailViewUIController and I swipe to right, it will show the DetailViewUIController of next item and when left, it will show the previous item.
I find a link which kind of do that in swift. But it only has 3 static subviewcontroller.
https://medium.com/swift-programming/ios-swipe-view-with-swift-44fa83a2e078
The number of entries in my TableView can be pretty long, how can I do the same thing dynamically, i.e. without having static number of subviewcontroller created and add as 'addChildViewController'?
Update:
Thanks again #rdelmar for your help.
I am having trouble getting the ' set the contentOffset of the collection view to (collection view width) * selectedRow' to work.
In my prepareForSegue function, I have added:
x = Float(indexPath.row) * 375.0
var point = CGPointMake(CGFloat(x), 0)
println ("***x=")
println (x)
detailController.collectionView?.setContentOffset(point , animated: false)
where detailController is UICollectionViewController.
and in the
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as DetailViewCell
// Configure the cell
println("DetailViewController in cellForItemAtIndexPath()")
println(indexPath.row)
cell.myitem = allItems[indexPath.row]
return cell
}
And I always see cellForItemAtIndexPath trying to get 0th element of the allItems (that is the whole collections of all my objects.
So setContentOffset() does not change what I am displaying in the Detail View regardless which item I click in my TableView to launch the Detail View.
Any idea to solve this?
Thank you.
There are a lot of different ways you could do this. One way would be to make your detail view controller's view be a collection view (with paging enabled), whose cells are the same size as the screen. You would need to pass in the array that you use to populate your table so the collection view could populate its cells. This would be quite efficient, since the collection view would only ever need to instantiate two cells.

Resources