How To Add Multiple Buttons To UITableViewCells Swift - ios

I am creating a simple iOS app with Xcode and Swift 5 where the user can pick a theme and then the background will change color. I want to accomplish this by adding buttons onto a table view's cells. I know how to add one button to a table view, but I have no idea how to put multiple buttons into a table view cell.
Here is a table view cell with a button I added to it:
p.s.: ignore the text inside of the button
What I'd want is for one button on each side and one in the middle, like this:
Here's my tableviewcontroller:
import UIKit
#objcMembers class TBController: UITableViewController {
var times = 0
var lastChar = ""
var subString = ""
var text = ""
override func viewDidLoad() {
super.viewDidLoad()
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return singleton.count
}
// 3
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
var cellButton: UIButton!
///////////////////
cellButton = UIButton(frame: CGRect(x: 5, y: 5, width: 50, height: 30))//5, 5, 50, 30
times = times + 1
cell.textLabel?.text = SingletonViewController.singleton[indexPath.row]
if times == 1{
cellButton.setTitle("Back", for: UIControl.State.normal)
cell.addSubview(cellButton)
cell.accessoryView = cellButton
cellButton.backgroundColor = UIColor.blue
cell.tag = 1
}else{
cell.tag = 2
}
return cell
}
}

You need to subclass UITableViewCell and create your own custom implementation, you can do this by code or by IB
This is the IB way of doing it:
When you create a new Cocoa Touch Class file, choose to subclass from UITableViewCell and select the create XIB file option. After that configure the XIB's view whoever you want and set a reuse identifier in the Identity Inspector.
If you want to have the layout from your screenshot, drag a UIStackView, set it's constraints to top, bottom, leading and trailing to the contentView of the cell, set the alignment to horizontal, then select fill equally and drag 3 UIButtons inside of it, the stackView will make sure to arrange them nicely
In your viewController don't forget to register the cell with the tableView, and then return that cell from cellForRowAtIndexPath

Related

UITableView Duplicate cells (custom cells with textfields)

I have spent days on resolving this issue and after trying much I am asking a question here. I am using a custom UITableViewCell and that cell contains UITextFields. On adding new cells to the table view, the table view behaves abnormal like it duplicates the cell and when I try to edit the textfield of new cell, the textfield of previous cel gets edited too.
The behavior of duplication is as follows: 1st cell is duplicated for 3rd cell. I don't know this is due to reusability of cells but could anyone tell me about the efficient solution?
I am attaching the screenshot of UITableViewCell.
The code for cellForRow is as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.dropDownViewProducts.index = indexPath.row
cell.txtDescription.index = indexPath.row
cell.tfPrice.index = indexPath.row
cell.dropDownQty.index = indexPath.row
cell.tfTotalPrice_Euro.index = indexPath.row
cell.tfTotalPrice_IDR.index = indexPath.row
cell.dropDownViewTotalDiscount.index = indexPath.row
cell.dropDownViewDeposit.index = indexPath.row
cell.tfTotalDeposit_Euro.index = indexPath.row
cell.tfRemaingAfterDeposit_IDR.index = indexPath.row
return cell
}
The issue is the cell is being reused by the UITableView, which is what you want to happen for good scrolling performance.
You should update the data source that supports each row in the table to hold the text the user inputs in the field.
Then have the text field's text property assigned from your data source in cellForRowAt.
In other words, the UITableViewCell is the same instance each time you see it on the screen, and so is the UITextField and therefore so is it's text property. Which means it needs to be assigned it's correct text value each time cellForRowAt is called.
I'm unsure of your code so I have provided an example of how I would do something like what you want:
class MyCell: UITableViewCell {
#IBOutlet weak var inputField: UITextField!
}
class ViewController: UIViewController {
#IBOutlet weak var table: UITableView!
var items = [String]()
fileprivate func setupItems() {
items = ["Duck",
"Cow",
"Deer",
"Potato"
]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupItems()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// the # of rows will equal the # of items
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we use the cell's indexPath.row to
// to get the item in the array's text
// and use it as the cell's input field text
guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as? MyCell else {
return UITableViewCell()
}
// now even if the cell is the same instance
// it's field's text is assigned each time
cell.inputField.text = items[indexPath.row]
// Use the tag on UITextField
// to track the indexPath.row that
// it's current being presented for
cell.inputField.tag = indexPath.row
// become the field's delegate
cell.inputField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
// or whatever method(s) matches the app's
// input style for this view
func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else {
return // nothing to update
}
// use the field's tag
// to update the correct element
items[textField.tag] = text
}
}
I suggest to do the following
class Product_PriceTableViewCell: UITableViewCell {
var indexRow: Int = -1
func configureCell(index: Int) {
cell.dropDownViewProducts.clean()
...
cell.tfRemaingAfterDeposit_IDR.clean()
}
}
where clean is the function to empty de view (depend on the type)
Then in the delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.configureCell(row: indexPath.row)
return cell
}
As #thefredelement pointed out when the cell is not in the view frame, it is not created. Only when the view is going to appear, it tries to reuse an instance of the cell and as the first is available, the table view uses it but does not reinitialize it. So you have to make sure to clean the data
The rest of the answer is for better coding.

didSelectRowAt indexPath is not being called

I created a UIViewController that contains two View (Top , Bottom),
the Bottom view expands all the way to the top when clicking on the searchBar. (bottomView height expands, topView height is getting smaller).
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
self.expandBottomView()
return true
}
func expandBottomView() {
let heightToAdd = TopView.frame.height - numOfRequestsTitle.frame.height
RecomandFriendHeight.constant += heightToAdd
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
topTableView.isUserInteractionEnabled = false
}
Both View contain TableViews (topTableView , BottomTableView) all delegates are set I checked.
BottomView Contains another button to collapse the bottomView when needed.
topTableViewCell contains two Buttons and two labels.
bottomTableViewCell contains a label and an imageView.
The Problem is none of the tableViews cells invokes didSelectRowAt.
If you add button to a cell didSelectAt not called on tapping the button. In this case you have to add a selector to this button inside cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! customcell
cell.btn.addTarget(self, action: #selector(clickedCell), for: .touchUpInside)
return cell
}
and implement this selector method
func clickedCell(_ sender : UIButton) {
print("clicked")
}
Make
topTableView.isUserInteractionEnabled = true

How to realize the tableView's cell margin the left and right periphery of the tableView?

This is my requirement:
I want my tableView's cell to be like the last cell, its border is margin the tableView some pix, not contradict the tableview's edge.(I want this is because when I click down the cell, there is gray effect on the cell)
How to do with that?
u can't resize the cell's, instead u can set the views's layer properties to achieve the similar effect, for example, (u are not mentioning which language u are using, i assume u are using swift).
i will assume your custom cell contains a UIView and some other view components, like below,
and also add outlet for imageHolderView in the above image,
out let name will be holderView as shown in below image,
in the custom cell class, define two methods for selection management, and your custom cell class would look like below,
class CustomCell: UITableViewCell {
#IBOutlet weak var circleNameTextField: UILabel!
#IBOutlet weak var holderView: UIView!
var cellindexPath:IndexPath?
var selectedIndexPath:IndexPath?
func selectTheCell() {
if self.selectedIndexPath?.row == self.cellindexPath?.row {
self.holderView.layer.cornerRadius = 6.0
self.holderView.layer.masksToBounds = true
self.holderView.layer.borderWidth = 4.0
self.holderView.layer.borderColor = UIColor.red.cgColor
self.backgroundColor = UIColor.gray
} else {
self.resetCellWith(animate: false)
}
}
func resetCellWith(animate:Bool) {
self.holderView.layer.cornerRadius = 0.0
self.holderView.layer.masksToBounds = false
self.holderView.layer.borderWidth = 0.0
self.holderView.layer.borderColor = UIColor.clear.cgColor
self.backgroundColor = UIColor.orange
}
}
now all u have to do is call the above methods, from controller and update the cell behaviour, for example,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selIndexPath = indexPath
self.aTableView.reloadSections(IndexSet(integer: 0), with: .none)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CustomCell? = tableView.dequeueReusableCell(withIdentifier: "CUSTOM_CELL", for: indexPath) as? CustomCell//tableView.dequeueReusableCell(withIdentifier: "CUSTOM_CELL") as? CustomCell
cell?.cellindexPath = indexPath
if let selectedIndexPath = self.selIndexPath {
cell?.selectedIndexPath = selectedIndexPath
cell?.selectTheCell()
} else {
cell?.resetCellWith(animate:false)
}
cell?.selectionStyle = .none
return cell!
}
with the above arrangement, u can get the table cell and selection like below,
NOTE: well, above is one way achieve this effect. and method names i simply used the sample project that i created for different purpose. :)

Implement a dynamic table within a static table view (cell)

So basically I have implemented a static table so far and at the very bottom, I want to add another dynamic table for the participant list of the event. Check out the screenshot here:
My problem now is, that I'm not able to setup the dynamic table with its own class which is placed in the last static table view cell. You can check out the red marked squares in the screenshot. My actual plan was to give the dynamic table its own class and then retrieve the participant list as an array and setup the numberOfRowsInSection according to the count of the array etc.
Do you guys have an idea how I can implement that within a static table? Basically, how can I add that dynamic table at the bottom, including later then endless scrolling?
I've tried this post so far but it didn't completely help me: Dynamic UITableView inside static cell
I'll add my solution below.
Your help would appreciated a lot!
Kind regards
Matt
Unless there's a real reason to have the top portion be a table as well, the easiest solution is going to be to make the top portion a table header view and just use a dynamic table.
Create a ViewController. Then place two ContainerViewControllers inside of the ViewController. Create segues to 2 separate tableViewcontrollers using embed. One to the static tableview and one to the dynamic tableview. This way you could have a static table on top and a dynamic one below.
Okay thank you rMickeyD for your tip. I used it partially and implemented it. You can see the result on this screenshot here:
Adding to this I set the height of the cell in the static table with a prepareToSegue to the array of the guests.count * 44 for the cell height in the right.
Code for the static table view (left):
import UIKit
class EventDetailTableViewController: UITableViewController {
var attendeesArrayFromDatabase = ["Kathi", "Cansin", "Tyrone", "Manuel", "Stavros", "Christoph", "Maria", "Aditya", "Kathi", "Cansin", "Tyrone", "Manuel", "Stavros", "Christoph", "Maria", "Aditya"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.section == 1 && indexPath.row == 0 {
let height:CGFloat = CGFloat(attendeesArrayFromDatabase.count * 44)
return height
}
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
// MARK: Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destViewController : EventPeopleJoinedContainerTableViewController = segue.destinationViewController as! EventPeopleJoinedContainerTableViewController
destViewController.attendeesArray = attendeesArrayFromDatabase
}
}
and the right tableview for the dynamic table:
import UIKit
class EventPeopleJoinedContainerTableViewController: UITableViewController {
var attendeesArray = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return attendeesArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("personJoined", forIndexPath: indexPath) as UITableViewCell
//Create a label with the name of a person from array attendeesArray
let attendeeNameLabel = UILabel(frame: CGRect(x: 70, y: 0, width: cell.frame.width, height: cell.frame.height))
attendeeNameLabel.font = cell.textLabel?.font.fontWithSize(14.0)
attendeeNameLabel.textColor = UIColor.darkGrayColor()
attendeeNameLabel.text = attendeesArray[indexPath.row] as? String
//Create a image view for the profile picture of the guest
let attendeeImageView = UIImageView(frame: CGRect(x: 25, y: 7, width: 30, height: 30))
attendeeImageView.layer.cornerRadius = attendeeImageView.frame.size.width/2
attendeeImageView.layer.borderColor = UIColor.whiteColor().CGColor
attendeeImageView.layer.borderWidth = 0.5
attendeeImageView.clipsToBounds = true
attendeeImageView.image = UIImage(named: "profilPicDummy")
cell.contentView.addSubview(attendeeNameLabel)
cell.contentView.addSubview(attendeeImageView)
return cell
}
}

How do I create a parallax effect in UITableView with UIImageView in their prototype cells

I'm building an app in iOS 8.4 with Swift.
I have a UITableView with a custom UITableViewCell that includes a UILabel and UIImageView. This is all fairly straight forward and everything renders fine.
I'm trying to create a parallax effect similar to the one demonstrated in this demo.
I currently have this code in my tableView.cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = self.tableView.dequeueReusableCellWithIdentifier("myitem", forIndexPath: indexPath) as! MixTableViewCell
cell.img.backgroundColor = UIColor.blackColor()
cell.title.text = self.items[indexPath.row]["title"]
cell.img.image = UIImage(named: "Example.png")
// ideally it would be cool to have an extension allowing the following
// cell.img.addParallax(50) // or some other configurable offset
return cell
}
That block exists inside a class that looks like class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource { ... }
I am also aware that I can listen to scroll events in my class via func scrollViewDidScroll.
Other than that, help is appreciated!
I figured it out! The idea was to do this without implementing any extra libraries especially given the simplicity of the implementation.
First... in the custom table view Cell class, you have to create an wrapper view. You can select your UIImageView in the Prototype cell, then choose Editor > Embed in > View. Drag the two into your Cell as outlets, then set clipToBounds = true for the containing view. (also remember to set the constraints to the same as your image.
class MyCustomCell: UITableViewCell {
#IBOutlet weak var img: UIImageView!
#IBOutlet weak var imgWrapper: UIView!
override func awakeFromNib() {
self.imgWrapper.clipsToBounds = true
}
}
Then in your UITableViewController subclass (or delegate), implement the scrollViewDidScroll — from here you'll continually update the UIImageView's .frame property. See below:
override func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY = self.tableView.contentOffset.y
for cell in self.tableView.visibleCells as! [MyCustomCell] {
let x = cell.img.frame.origin.x
let w = cell.img.bounds.width
let h = cell.img.bounds.height
let y = ((offsetY - cell.frame.origin.y) / h) * 25
cell.img.frame = CGRectMake(x, y, w, h)
}
}
See this in action.
I wasn't too happy with #ded's solution requiring a wrapper view, so I came up with another one that uses autolayout and is simple enough.
In the storyboard, you just have to add your imageView and set 4 constraints on the ImageView:
Leading to ContentView (ie Superview) = 0
Trailing to ContentView (ie Superview) = 0
Top Space to ContentView (ie Superview) = 0
ImageView Height (set to 200 here but this is recalculated based on the cell height anyway)
The last two constraints (top and height) need referencing outlets to your custom UITableViewCell (in the above pic, double click on the constraint in the rightmost column, and then Show the connection inspector - the icon is an arrow in a circle)
Your UITableViewCell should look something like this:
class ParallaxTableViewCell: UITableViewCell {
#IBOutlet weak var parallaxImageView: UIImageView!
// MARK: ParallaxCell
#IBOutlet weak var parallaxHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var parallaxTopConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
clipsToBounds = true
parallaxImageView.contentMode = .ScaleAspectFill
parallaxImageView.clipsToBounds = false
}
}
So basically, we tell the image to take as much space as possible, but we clip it to the cell frame.
Now your TableViewController should look like this:
class ParallaxTableViewController: UITableViewController {
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return cellHeight
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! ParallaxTableViewCell
cell.parallaxImageView.image = … // Set your image
cell.parallaxHeightConstraint.constant = parallaxImageHeight
cell.parallaxTopConstraint.constant = parallaxOffsetFor(tableView.contentOffset.y, cell: cell)
return cell
}
// Change the ratio or enter a fixed value, whatever you need
var cellHeight: CGFloat {
return tableView.frame.width * 9 / 16
}
// Just an alias to make the code easier to read
var imageVisibleHeight: CGFloat {
return cellHeight
}
// Change this value to whatever you like (it sets how "fast" the image moves when you scroll)
let parallaxOffsetSpeed: CGFloat = 25
// This just makes sure that whatever the design is, there's enough image to be displayed, I let it up to you to figure out the details, but it's not a magic formula don't worry :)
var parallaxImageHeight: CGFloat {
let maxOffset = (sqrt(pow(cellHeight, 2) + 4 * parallaxOffsetSpeed * tableView.frame.height) - cellHeight) / 2
return imageVisibleHeight + maxOffset
}
// Used when the table dequeues a cell, or when it scrolls
func parallaxOffsetFor(newOffsetY: CGFloat, cell: UITableViewCell) -> CGFloat {
return ((newOffsetY - cell.frame.origin.y) / parallaxImageHeight) * parallaxOffsetSpeed
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY = tableView.contentOffset.y
for cell in tableView.visibleCells as! [MyCustomTableViewCell] {
cell.parallaxTopConstraint.constant = parallaxOffsetFor(offsetY, cell: cell)
}
}
}
Notes:
it is important to use tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) and not tableView.dequeueReusableCellWithIdentifier("CellIdentifier"), otherwise the image won't be offset until you start scrolling
So there you have it, parallax UITableViewCells that should work with any layout, and can also be adapted to CollectionViews.
This method works with table view and collection view.
first of all create the cell for the tableview and put the image view in it.
set the image height slightly more than the cell height. if cell height = 160 let the image height be 200 (to make the parallax effect and you can change it accordingly)
put this two variable in your viewController or any class where your tableView delegate is extended
let imageHeight:CGFloat = 150.0
let OffsetSpeed: CGFloat = 25.0
add the following code in the same class
func scrollViewDidScroll(scrollView: UIScrollView) {
// print("inside scroll")
if let visibleCells = seriesTabelView.visibleCells as? [SeriesTableViewCell] {
for parallaxCell in visibleCells {
var yOffset = ((seriesTabelView.contentOffset.y - parallaxCell.frame.origin.y) / imageHeight) * OffsetSpeedTwo
parallaxCell.offset(CGPointMake(0.0, yOffset))
}
}
}
where seriesTabelView is my UItableview
and now lets goto the cell of this tableView and add the following code
func offset(offset: CGPoint) {
posterImage.frame = CGRectOffset(self.posterImage.bounds, offset.x, offset.y)
}
were posterImage is my UIImageView
If you want to implement this to collectionView just change the tableView vairable to your collectionView variable
and thats it. i am not sure if this is the best way. but it works for me. hope it works for you too. and let me know if there is any problem
After combining answers from #ded and #Nycen I came to this solution, which uses embedded view, but changes layout constraint (only one of them):
In Interface Builder embed the image view into a UIView. For that view make [√] Clips to bounds checked in View > Drawing
Add the following constraints from the image to view: left and right, center Vertically, height
Adjust the height constraint so that the image is slightly higher than the view
For the Align Center Y constraint make an outlet into your UITableViewCell
Add this function into your view controller (which is either UITableViewController or UITableViewControllerDelegate)
private static let screenMid = UIScreen.main.bounds.height / 2
private func adjustParallax(for cell: MyTableCell) {
cell.imageCenterYConstraint.constant = -(cell.frame.origin.y - MyViewController.screenMid - self.tableView.contentOffset.y) / 10
}
Note: by editing the magic number 10 you can change how hard the effect will be applied, and by removing the - symbol from equation you can change the effect's direction
Call the function from when the cell is reused:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCellId", for: indexPath) as! MyTableCell
adjustParallax(for: cell)
return cell
}
And also when scroll happens:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
(self.tableView.visibleCells as! [MyTableCell]).forEach { cell in
adjustParallax(for: cell)
}
}

Resources