How to left/right-align custom UITableView cell - ios

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

Related

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

swift toggle between different table view cell layouts

I wonder what techniques there are when it comes to toggle between different layouts in a table view.
What I have right now is an embedded tableview/custom cell inside my VC.
It has a simple list design, an image / title.
What I would like to do is when the user press the "grid" button it will change the layout into a bigger cell, kinda like how instagram looks.
So is it possible to animate between different cell layouts in a tableview?
thanks!
I don't believe that it's possible to change the style once it has been initialised. You can however change between your own custom cells at runtime and is probably best achieved using a protocol for different cell types.
Yes it is possible. I have used it but I am not saying that this is the ultimate way.
Here are the steps you may like to follow :
Design two different cells with different identifiers(I did it from storyboard)
In cellForRowAtIndexPath method, check for the identifiers and display the layouts accordingly.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = (self.layoutSegmentControl.selectedSegmentIndex == 0) ? "gridLayoutCell" : "listLayoutCell" //I used segment control to toggle, change the condition as per your need
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! MyCustomTableViewCell
if(cellIdentifier == "gridLayoutCell")
{
//Set the values
cell.bigImgView.image = [yourimage] //for ex
...
}
if(cellIdentifier == "listLayoutCell")
{
//Set the values
cell.thumbnailImgView.image = [yourimage] //for ex
...
}
}
reload your tableview when toggling(layout changes)
Try this and let me know. Hope it works for you!

Swift MVC Design With Multiple Table Views

So I have the main view of my controller that has a table view. This table view will be displaying many different custom classes that subclass UITableViewCell. Some of these cells will ALSO have table views inside of them.
My problem is that I do not know what class I should assign to to be the UITableViewDelegate in this sort of situation for the table view in the table view cell. My intial thought was to make it the cell view class:
class MyTableViewCell: TableViewCell {
#IBOutlet var tableView: UITableView!;
var messages: Array<String>?;
//called by parent tableview when cellForRowAtIndexPath is called in main controller
//to initialize view with dynamic properties at run time
override func render(obj: MyObject) {
messages = obj.getMessages();
}
}
extension MyTableViewCell: UITableViewDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages == nil ? 0 : messages!.count;
}
}
This is problematic because I have no where to register my nib files to the table view:
nib = UINib(nibName: "MyTableViewCell", bundle: nil);
self.tableView.registerNib(nib!, forCellReuseIdentifier: "custom");
Also, I feel like making a view a table view data source is violating MVC principles. What is the best way to go about with my table views within table view cells?
The UITableView within a UITableCell was indeed implemented in Pulse as described by genalipsis. In Obj-C,there is a full tutorial plus posted code located here for UITableView within a UITableCell:
http://iosstuff.wordpress.com/2011/06/29/adding-a-uitableview-inside-a-uitableviewcell/
http://iosstuff.wordpress.com/2011/06/29/creating-pulse-style-scrolling-horizontally-scrolling-uitableview-as-a-subview-of-uitableviewcell/
The was done in Xcode 4. I am not sure if this will work in Xcode 6.1 but it does describe a methodology.
I think an even more descriptive and easier to follow tutorial was posted at Ray Wenderlich's site here:
http://www.raywenderlich.com/4680/how-to-make-an-interface-with-horizontal-tables-like-the-pulse-news-app-part-1
http://www.raywenderlich.com/4723/how-to-make-an-interface-with-horizontal-tables-like-the-pulse-news-app-part-2
Create a regular UITableView
Create a custom UITableViewCell
Add a rotated UITableView as a subview of our UITableViewCell
Create another custom UITableViewCell for our articles
Rotate our custom cell and use it for our horizontal table view
While the tuorial was from 2011, some of the comments are very recent, so the approach must still work.
There is also a github project that references a stack overflow discussion from earlier this year:
https://github.com/hefgi/TableViewInTableViewCell
If you open the project, the storyboard file for the iPhone is instructive (Main_iPhone.storyboard):
A table view inside of a table view has been done before. One of the first iPad Apps, Pulse, used this strategy to allow uses to scroll vertically between RSS feeds and horizontally, within each cell, between RSS entries. That is, the embedded table view was rotated and the cells inside it where also rotated so that their orientation allowed reading.
For usability reasons, you will probably want to follow a similar pattern, else it might be difficult to scroll.
Tentative architecture: MainTableViewController is the controller for the table view that contains sub table views. SubTableViewController is the controller for the table views contained within the cells of the MainTableViewController. This approach lets you use TableViewControllers in the "standard" fashion
class MainTableViewController: TableViewController {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//dequeue a cell or create an instance of a cell
//create instances of SubTableViewController or change the data source for indexPath
//add the view of SubTableViewController to the cell's view hierarchy
//make necessary view adjustments depending on orientation, etc.
}
Not a standard setup, so I would expect that a few further hacks will be necessary.

Resources