Swift UI collection view layout - ios

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.

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

Can I use a single custom cell for multiple different cells?

I have created a single prototype cell which has two labels (mainLabel and subLabel) and an uiimageview. In the uitableview I'd like to have several cells which reuse the prototype and when needed the subLabel is hidden and the uiimageview is changed with different one or with a uiswitch. The two labels have different text for each cell. Do you have any suggestions/hints in order to do it? possibly in a mvvm architecture?
I'll describe what I am doing:
I have a struct (the Model) with two properties: label and sublabel. This is then instantiate by a viewModel which provides text for each cell, done by a method called getModel(_ indexPath: IndexPath) -> cellModel { ... }. Finally in UIViewController, in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) { ... } I am calling getModel(), using dequeueReusableCell and setting up each cell.
In getModel() there is a huuuuge switch which I use to know which cell is which
Then in uitableviewcell I have some method that hides sublabel and changes uiimageview.
It kind of works, however I have some issues with while scrolling. For example, sometimes a uiimageview is drawn in another cell, or a subLabel is hidden, even if it is not supposed to. I guess this is due because it is reusing the cell, and I am not resetting it.
Anyway, any suggestions or ideas?
I know this is overkilling...
No need for any pattern. Yes, you can use that single cell design for all cells. Just hide/empty label(s) and image view as you like per cell.
First of all you have to set default value to both the labels and imageview
i.e. (consider a title label, a sub label and a imageview)
lblTitle.isHidden = false
lblSubLabel.isHidden = false
imgViewIcon.image = nil
Then just show labels in specific condition that you want to match and set image in imageview
i.e. (consider your condition to hide sub label)
if needToHide == true {
lblSubLabel.isHidden = true
}

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

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.

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.

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