I am working on the following :
Screenshot
Basically it's one UIImageView inside of another UIImageView at the top (60% of the screen) and a UITableView that takes 40% of the screen
and code (see below) :
What I am trying to achieve is to resize the cells of the UITableView full screen when my customCell is clicked on and unsize it to it's original state when unclicked.
Is there any way to make this work?
Thanks in advance.
//Label outlets
#IBOutlet weak var tableView: UITableView!
var selectedIndexPath : IndexPath? = nil
override func viewDidLoad() {
super.viewDidLoad()
//MARK Start of CustomViewCell Code
tableView.register(UINib(nibName: "CustomViewCell", bundle: nil), forCellReuseIdentifier: "Cell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! CustomViewCell
cell.clipsToBounds = true
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let index = indexPath
if selectedIndexPath != nil {
if index == selectedIndexPath {
return 667
} else {
return 62
}
} else {
return 62}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch selectedIndexPath {
case nil:
selectedIndexPath = indexPath
default:
if selectedIndexPath! == indexPath {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
//MARK End of CustomViewCell Code
Besides reloading the cells with the new heights, you also need to change the frame of the table view. If you want to make the table occupy the whole screen, make sure you create an IBOutlet for it, say myTableView, then, after:
tableView.reloadRows(at: [indexPath], with: .automatic)
Add this code:
myTableView.frame = view.bounds
You may also create a Bool variable to keep track of normal and full-screen modes of the table view, and some CGRect variable to keep the initial frame. You may need something like this when you want the table view to go back to its initial frame:
myTableView.frame = initialTableViewFrame
Hope you get the idea.
Here is a pure auto-layout approach:
1.In your interface builder, create a UIImageView with constraints [Trailing, Leading, Top , Bottom, Height], make the height >= and priority is 999. You will use this height later in code for the expand/collapse logic.
2.Create a dummy UIView whose height will be programmatically set later, this UIView will 'push' / 'stretch' your cell height when you create the expand effect to your imageview. Set it's visibility to hidden since you don't need to show this to your user.
3.In your custom cell, create two NSLayoutConstraint instances as #IBoutlets, one for UIImageView height and one for the dummy UIView height then connect them later in your interface file.
let lowPriorityHeight : Float = 250;
let highPriorityHeight : Float = 999;
class CustomCell: UITableViewCell {
#IBOutlet weak var imgVwHeight: NSLayoutConstraint!
#IBOutlet weak var tempHeight : NSLayoutConstraint!
var isExpanded = false{
didSet{
imgVwHeight.priority = (isExpanded) ? lowPriorityHeight : highPriorityHeight;
if isExpanded == true{
tempHeight.constant = ExpandedHeight
}else{
tempHeight.constant = NormalHeight
}
}
}
4.In your main class, set the isExpanded to true or false to create the effect.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
cell.isExpanded = !cell.isExpanded
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CustomCell = tableView.dequeueReusableCell(withIdentifier: "kCustomCell", for: indexPath) as! CustomCell
return cell
}
You may download my sample implementation HERE
Related
When scrolling off the screen with my custom cell in an expanded state the buttons are being hidden which should not be the case. Beneath the labels, 2 buttons are not appearing when scrolling up again. I'm guessing something needs to happen after the cell has been dequeued. Any help would be much appreciated.
I have a custom cell where initially the height for a row is 80, upon clicking on the cell it expands to 120. By default I have the buttons hidden like so:
#IBOutlet weak var followButton: UIButton! {
didSet {
followButton.isHidden = true
}
}
#IBOutlet weak var blockButton: UIButton! {
didSet {
blockButton.isHidden = true
}
}
I have an var expandedIndexSet : IndexSet = [] which tracks which cell has been expanded.
The method below updates indexSet accordingly.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.beginUpdates()
let cell = tableView.cellForRow(at: indexPath) as! StackOverflowTableViewCell
if(expandedIndexSet.contains(indexPath.row)){
expandedIndexSet.remove(indexPath.row)
} else {
expandedIndexSet.insert(indexPath.row)
}
cell.blockButton.isHidden = !cell.blockButton.isHidden
cell.followButton.isHidden = !cell.followButton.isHidden
tableView.endUpdates()
}
The height gets adjusted like so:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if expandedIndexSet.contains(indexPath.row) {
return 140
}
else {
return 80
}
}
The following snippet might be helpful, please try.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ....
cell.followButton.isHidden = !expandedIndexSet.contains(indexPath.row)
cell.blockButton.isHidden = cell.followButton.isHidden
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Update View Model
if(expandedIndexSet.contains(indexPath.row)){
expandedIndexSet.remove(indexPath.row)
} else {
expandedIndexSet.insert(indexPath.row)
}
// Table View Animation
tableView.beginUpdates()
tableView.reloadRows(at: indexPaths, with: .automatic)
tableView.endUpdates()
}
I was wondering if there any possible way to create a table view with this style:
I have a dictionary contains title and image values, I need to create a cell one Image-Right / Title-Left and next vice versa. How can achieve something like this?
You can do it by setAffineTransform in this way:
• build up your tableView with one prototype cell that has an image in left and a label in right
• then do this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier", for: indexPath) as! YourTableViewCell
if (indexPath.row % 2 == 0) {
cell.contentView.layer.setAffineTransform(CGAffineTransform(scaleX: -1, y: 1))
cell.YourImage.layer.setAffineTransform(CGAffineTransform(scaleX: -1, y: 1))
cell.YourLabel.layer.setAffineTransform(CGAffineTransform(scaleX: -1, y: 1))
}
// do what ever you want ...
return cell
}
also the best solution is defining 2 prototype cells but in your case this is a tricky and fast way to achieve your goal.
Yes, you can use a table view to achieve your requirement. you will need to follow the following steps.
Method 1:
Create two table view cell XIB's one with left side label and right side image, the second one is with left side image and right side image.
Keep same class of both the XIB's you have created but with different identifiers.
In your Table view cellForRowAtIndexPath method implement following logic.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasourceArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row % 0 == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "RightLabelTableViewCell", for: indexPath) as! CustomTablViewCell
cell.model = datasourceArray[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "LeftLabelTableViewCell", for: indexPath) as! CustomTablViewCell
cell.model = datasourceArray[indexPath.row]
return cell
}
}
}
Note: You can use one class for TableViewCell with a different
identifier and design your xib's accordingly.
Method 2:
Flip your table view cell's content view in a such a way that they will swap in your UI.
add the following code into your cellForRowAtIndexPath and also add else part of it because cell for a row may behave weirdly because of dequeing:
extension UIView {
/// Flip view horizontally.
func flipX() {
transform = CGAffineTransform(scaleX: -transform.a, y: transform.d)
}
}
Usage:
cell.contentView.flipX()
cell.yourImage.flipX()
cell.youImageName.flipX()
Don't forget to add else part in cellForRowAt method.
There are actually many ways of doing this:
Create 2 cells. Have 2 cells like OddTableViewCell and EvenTableViewCell. You can choose with index path which to use in cellForRow method like:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row%0 == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "EvenTableViewCell", for: indexPath) as! EvenTableViewCell
cell.model = dataArray[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "OddTableViewCell", for: indexPath) as! OddTableViewCell
cell.model = dataArray[indexPath.row]
return cell
}
}
}
Have a single cell but duplicate views so you have 2 labels and 2 image views. Then hide them as you need to:
class MyCell: UITableViewCell {
#IBOutlet private var leftImageView: UIImageView?
#IBOutlet private var rightImageView: UIImageView?
#IBOutlet private var leftLabel: UILabel?
#IBOutlet private var rightLabel: UILabel?
var userImage: UIImage? {
didSet {
refresh()
}
}
var userName: String? {
didSet {
refresh()
}
}
var imageOnLeft: Bool = false {
didSet {
refresh()
}
}
func refresh() {
leftImageView?.image = imageOnLeft ? userImage : nil
leftImageView?.isHidden = !imageOnLeft
rightImageView?.image = imageOnLeft ? nil : userImage
rightImageView?.isHidden = imageOnLeft
leftLabel?.text = imageOnLeft ? nil : userName
leftLabel?.isHidden = imageOnLeft
rightLabel?.text = imageOnLeft ? userName : nil
rightLabel?.isHidden = !imageOnLeft
}
}
Have a single cell with stack view. Add a label and image view onto the stack view. You can change order of items in stack view. Some promising answer can be found here. The rest should be pretty similar to the second solution.
(4.) Also you could just use a collection view and have a label cell and an image cell.
Create one cell with 2 image and 2 label left and right
when you went to left side image that time hide right side image same as in label.
cell
import UIKit
class TestTableViewCell: UITableViewCell {
#IBOutlet weak var lbl_left: UILabel!
#IBOutlet weak var lbl_right: UILabel!
#IBOutlet weak var img_right: UIImageView!
#IBOutlet weak var img_left: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure_cell(left:Bool)
{
if left{
img_left.isHidden = true
img_right.isHidden = false
lbl_left.isHidden = false
lbl_right.isHidden = true
self.img_right.image = UIImage(named: "testimg")
}else{
img_left.isHidden = false
img_right.isHidden = true
lbl_left.isHidden = true
lbl_right.isHidden = false
self.img_left.image = UIImage(named: "testimg")
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController
extension ViewController:UITableViewDataSource,UITableViewDelegate
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as? TestTableViewCell
if (indexPath.row + 1) % 2 == 0 {
cell?.configure_cell(left: true)
} else {
cell?.configure_cell(left: false)
}
return cell!
}
}
I have a table view that's in a nib file and it loads the data properly. It loads the required number of cells and fills in the data correctly. The cell is a xib file as well that includes multiple views. Each cell has a custom height. I managed to set the right height per cell using:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return (data[indexPath.row].height + 200)
}
The problem is that on initial load the cells don't get drawn properly like so:
Initial load image
The description area doesn't move where I made it move by changing its X and Y coordinates.
This gets fixed however when I scroll the table view beyond the initially loaded cells and go back to them, like so:
After scrolling
Upon loading the cell I move the description area below the image. This works for cells but not on the initially loaded ones. It only gets fixed once I scroll the broken cells out of view and go back to them. How do I fix this? How can I make it so that the cells get drawn properly on the initial load?
Edit: To clarify: The cells get loaded properly but aren't drawn correctly. I have to scroll out of the first few cells and scroll back to them for the cells to be drawn right.
Any help is appreciated. Thank you!
I can see that the image you'r trying to load is of larger dimension than that of the image container view.
You can try giving clipsToBound = True to the imageView.
This would most probably resolve your issue.
Thanks.
you want to increase UITableViewCell height based on image inside it, right?
Here is tableview datasource and delegate.
extension MyDataSource: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row] {
return height
} else {
return 100
}
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row] {
return height
} else {
return 100
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mydatas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let mydata = mydatas[indexPath.row]
var cell: UITableViewCell
cell = tableView.dequeueReusableCell(withIdentifier: 'myCell.cellIdentify()')!
cell.contentView.backgroundColor = UIColor.clear
cell.backgroundColor = UIColor.clear
cell.tag = indexPath.row
cell.selectionStyle = .none
let image = UIImage(named: promotion)
(cell as! myCell).configureCell(downloadImage: image!)
let aspectRatio = (image?.size.height)!/(image?.size.width)!
let imageHeight = (cell.contentView.frame.width * aspectRatio) + 16
UIView.performWithoutAnimation {
self.myTableView?.beginUpdates()
self.rowHeights[indexPath.row] = imageHeight
self.myTableView?.endUpdates()
}
return cell
}
}
here is UITableViewCell custom class.
myCell
import UIKit
class myCell: UITableViewCell {
#IBOutlet weak var imageHolder: WMView!
#IBOutlet weak var actionImage: UIImageView!
#IBOutlet weak var loaderIndicator: UIActivityIndicatorView!
override func prepareForReuse() {
super.prepareForReuse()
self.loaderIndicator.isHidden = false
self.loaderIndicator.startAnimating()
self.actionImage.image = nil
}
static func cellHeight() -> CGFloat {
return 73.0
}
static func cellIdentify() ->String {
return "myCell"
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configureCell(downloadImage: UIImage) {
self.imageHolder.layer.borderWidth = 1
self.imageHolder.layer.cornerRadius = 5
self.imageHolder.layer.borderColor = UIColor.clear.cgColor
self.imageHolder.layer.masksToBounds = true
self.imageHolder.clipsToBounds = true
//ImageHolder
self.actionImage.image = downloadImage
self.loaderIndicator.isHidden = true
}
}
My question is related to this one: How to change height Constraint of UIView in UitableviewCell when using UITableViewAutomaticDimension
The solution there does not seem to be working for me.
In the image above i have a simple cell.
On tap of cell, i want to change the constraint of the redView to be larger. This should then automatically change the height of the cell.
I already have the height constraint of the cell set to an #IBOutlet and i think i am correctly changing the size of the cell, but it is not working.
Here is my sample app that is not working. Any help? SampleApp - for Xcode 9.3
You need to set a bottom constraint to the red view so auto-layout can stretch the cell after setting the constant value
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! customcell
configure(cell: cell, indexPath: indexPath)
cell.redview.backgroundColor = .red
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! customcell
cell.constraint.constant = data[indexPath.row] == "contracted" ? 30 : 200
data[indexPath.row] = data[indexPath.row] == "contracted" ? "expanded" : "contracted"
tableView.reloadData()
}
func configure(cell: customcell, indexPath: IndexPath) {
let data = self.data[indexPath.row]
if data == "expanded" {
cell.constraint.constant = 200
} else {
cell.constraint.constant = 30
}
cell.layoutIfNeeded()
}
}
Calling the below will recalculate the heights.
[tableView beginUpdates];
[tableView endUpdates];
I have a UITableView with 3 prototyped cells (ex. 1st cell: image, 2nd cell: Description, 3. Links,...).
I would like to hide them if for a cell the data from the backend is empty (Ex. if there is no image, hide the first cell). In order to do that, I have override the heightForRowAtIndexPath function in this way:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch (indexPath.row) {
case 0:
if event?.photo_urls.count == 0{
return 0
}
else{
return 80.0
}
case 1:
if event?.description == ""{
return 0
}
else{
return 90.0
}
default:
return 100.0
}
}
and hidden the cell by doing
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch (indexPath.row) {
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier("PhotoCell", forIndexPath: indexPath) as! UITableViewCell
if event?.photo_urls.count != 0 {
// ...
}
else{
cell.hidden = true
}
return cell
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("DesCell", forIndexPath: indexPath) as! UITableViewCell
if event?.description != "" {
// ...
}
else{
cell.hidden = true
}
return cell
default:
let cell = tableView.dequeueReusableCellWithIdentifier("PhotoCell", forIndexPath: indexPath) as! UITableViewCell
return cell
}
}
Until here no problem, it works properly!
Now, THE PROBLEM is that I would like to make the cells dynamics according to the cell contents (ex. description height). In order to do that, I have used
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80.0
}
and if I comment the heightForRowAtIndexPath the cells are actually dynamics but I can't hide them anymore.
Do you have any suggestion on how to be able to hide the cells if they are empty and apply the automatic dimension according to their content?
lets say you have dynamic data and you want to show it in tableview so you need to create an array of your data to display.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet
var tableView: UITableView
var items: [String] = ["We", "Heart", "nothing" ,"" ,"imageurl", "", "xyz"]
override func viewDidLoad() {
super.viewDidLoad()
reloadTableAfterSorting()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func reloadTableAfterSorting(){
for var i = 0; i < self.items.count; i++
{
if self.items[i] == ""{
self.items.removeAtIndex(2)
}
}
self.tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("You selected cell #\(indexPath.row)!")
}
}
For that i recommend you to sort the array before displaying it in the table view. Hiding the cell is not a good idea and its not good according to Apple recommendations. So you can do one thing except hiding the cell: remove the index from the array. In this way you can always have data to show in table and it will behave properly. So don’t try to hide the cell just pop the index from array.