Add subview on tableView cells causes a sad patchwork - ios

I want to give a chat aspect to a table view of messages in my iPhone app.
In order to perform a quality render I add two subviews: the text and the "container" which is just a view with background color.
Even if it works the first time, when I scroll, it becomes really messy because it keeps adding lots of subviews.
Here you can see it when clean
And then when it has become messy
Here is the function to handle the transform, it's called when scrolling.
func configChatCell(cell: UITableViewCell, text: String, color:UIColor)
{
cell.textLabel?.numberOfLines = 0
cell.textLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping
cell.textLabel?.textColor = UIColor.whiteColor()
let fixedWidth = cell.bounds.width - 150
let textView: UITextView = UITextView(frame: CGRect(x: 0, y: 0, width: 10, height: CGFloat.max))
textView.text = text
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = textView.frame
newFrame.size = CGSize(width: min(newSize.width, fixedWidth), height: newSize.height)
textView.sizeThatFits(newFrame.size)
textView.frame = newFrame
textView.backgroundColor = UIColor.clearColor()
self.rowHeight = textView.frame.height+20
let view = UIView()
view.backgroundColor = color
print(textView.frame.height+10)
view.frame = CGRect(x: 0, y: 5, width: textView.frame.width+50, height: textView.frame.height+10)
view.layer.cornerRadius = 5
cell.backgroundColor = UIColor.clearColor()
cell.contentView.addSubview(view)
cell.contentView.addSubview(textView)
cell.contentView.sendSubviewToBack(view)
}
If I remove the subviews each time I scroll, nothing appears on screen.
Can somebody help me to find a solution? Or is there any other way to do this?
Thanks in advance!

I quickly wrote up something for this.
It starts with the ChatCell
class ChatCell: UITableViewCell {
var messageLabel: UILabel? {
didSet {
messageLabel?.text = message
}
}
var message: String? {
didSet {
messageLabel?.text = message
}
}
class func messageCell(withText text: String, leading: Bool = true) -> ChatCell {
let cell = ChatCell()
cell.message = text
// Make the container
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(container)
container.topAnchor.constraintEqualToAnchor(cell.contentView.topAnchor, constant: 8).active = true
container.bottomAnchor.constraintEqualToAnchor(cell.contentView.bottomAnchor, constant: -8).active = true
if leading {
container.leadingAnchor.constraintEqualToAnchor(cell.contentView.leadingAnchor, constant: leading ? 8 : 8*8).active = true
container.trailingAnchor.constraintLessThanOrEqualToAnchor(cell.contentView.trailingAnchor, constant: leading ? -8*8 : -8).active = true
} else {
container.leadingAnchor.constraintGreaterThanOrEqualToAnchor(cell.contentView.leadingAnchor, constant: leading ? 8 : 8*8).active = true
container.trailingAnchor.constraintEqualToAnchor(cell.contentView.trailingAnchor, constant: leading ? -8*8 : -8).active = true
}
// Make the messageLabel.
let messageLabel = UILabel()
messageLabel.numberOfLines = 0
messageLabel.textColor = UIColor.whiteColor()
messageLabel.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(messageLabel)
// Add constraints.
messageLabel.topAnchor.constraintEqualToAnchor(container.topAnchor, constant: 8).active = true
messageLabel.bottomAnchor.constraintEqualToAnchor(container.bottomAnchor, constant: -8).active = true
messageLabel.leadingAnchor.constraintEqualToAnchor(container.leadingAnchor, constant: 8).active = true
messageLabel.trailingAnchor.constraintEqualToAnchor(container.trailingAnchor, constant: -8).active = true
cell.messageLabel = messageLabel
container.backgroundColor = UIColor(red:0.19, green:0.70, blue:1.00, alpha:1.00)
container.layer.cornerRadius = 12.0
return cell
}
}
The cell also includes support for leading and trailing messages, for back and forth conversation. Perhaps make an array of tuples like this:
let messages: [(message: String, leading: Bool)] = [("Hello", true), ("My name is John Doe and this works quite well", false), ("I would agree", true)]
Then in your tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell you could do this:
let cell = ChatCell.messageCell(withText: messages[indexPath.row].message, leading: messages[indexPath.row].leading)
return cell
Let me know if this works for you. I tested it in a Playground and it works as expected

Assuming that your configureChatCell is called from tableView:cellForRowAtIndexPath:, then #Paulw11 is right; cells are reused, so you should only make changes that are unique to that row in the table. In your example, the only calls that you should be making in your method are textView.text = text and the ones to resize the textView to fit. Everything else should go in a dynamic cell prototype in the storyboard or, if you want to do everything in code (which I have a bad feeling you do), then put the rest of the configuration in a UITableViewCell subclass, then register that subclass with your table view.

I write something like this. It's simple but can elucidate solution
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseid", forIndexPath: indexPath)
// Configure the cell...
let contentView = cell.contentView
let subviews = contentView.subviews
for view in subviews {
if 100 == view.tag {
let label = view as! UILabel
label.text = self.datas[indexPath.row]
} else if 200 == view.tag {
view.layer.cornerRadius = 10.0
}
}
return cell
}
the key point is config every thing in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
the view in code with tag 200 is a background view has same frame with label, I use layout constraint in storyboard to make sure its size and position.

Related

ScrollView Doesn't Scroll in collectionViewCell

I am creating a collage app. The functionality I am looking at in my collectionViewCell is exactly like this: How to make a UIImage Scrollable inside a UIScrollView?
What I have done till now:
Created a custom collectionViewCell embedded a UIScrollView in it and an imageView inside the scrollView. I have set their constraints. Following is my code for collectionViewCell :
override init(frame: CGRect) {
super.init(frame: frame)
// addSubview(scrollView)
insertSubview(scrollView, at: 0)
contentView.isUserInteractionEnabled = true
scrollView.contentMode = .scaleAspectFill
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(imageView)
scrollContentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.isScrollEnabled = true
imageView.isUserInteractionEnabled = false
scrollView.delegate = self
scrollView.maximumZoomScale = 5.0
scrollView.backgroundColor = .red
scrollViewDidScroll(scrollView)
// scrollViewWillBeginDragging(scrollView)
let constraints = [
// Scroll View Constraints
scrollView.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0.0)
scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor,constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0.0),
scrollView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0.0),
// ImageView Constraints
imageView.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
imageView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
imageView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor),
imageView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor)
]
NSLayoutConstraint.activate(constraints)
}
Another issue I face here is if I do insertSubview() instead of addSubview my collectionView function for didselectItemAt works fine else if I use addSubView the didselectItemFunction doesn't execute. Code For didSelectItem :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if selectedImage[indexPath.row] != UIImage(named: "empty-image") {
collectionViewCellClass.scrollView.isUserInteractionEnabled = true
collectionViewCellClass.scrollViewDidScroll(collectionViewCellClass.scrollView)
collectionViewCellClass.scrollView.showsHorizontalScrollIndicator = true
}else {
collectionViewCellClass.imageView.isUserInteractionEnabled = false
self.currentIndex = indexPath.row
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
imagePicker.delegate = self
imagePicker.allowsEditing = false
imagePicker.sourceType = .savedPhotosAlbum
present(imagePicker, animated: true, completion: nil)
}
}
}
Code for CellForItemAt IndexPath :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imageView.image = selectedImage[indexPath.item]
// TODO: Round corners of the cell
cell.scrollView.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height)
// cell.scrollView.contentSize = CGSize(width: cell.imageView.image!.size.width * 2, height: cell.imageView.image!.size.height * 2)
cell.scrollView.contentSize = cell.imageView.image?.size ?? .zero
cell.scrollView.contentMode = .center
cell.scrollView.isScrollEnabled = true
// cell.scrollContentView.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height)
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.imageView.image!.size.width, height: cell.imageView.image!.size.height)
// cell.imageView.clipsToBounds = true
cell.backgroundColor = .black
return cell
}
I have tried changing the size of ImageView = cell.frame.size still no luck and changed the size for scrollView Frame as well to imageView.image.size but for some reason scrollView doesn't scroll.
Insertview vs addSubview
addSubview: add the view at the frontmost.
insertSubview: add the view at a specific index.
when you add your scrollview it using addSubView method it will block all the user interaction for the view's that are below it. That's why didSelect event is not triggered.
see this question
To make the scrollview scroll you need to set its contentSize which should be greater than scrollview's frame. try adding an image whose size is greater than collectionview cell.
scrollView.contentSize = imageView.bounds.size
Try adding the scrollView to the cells contentView:
self.contentView.addSubview(scrollView)
I finally found the Solution, following is what worked for me :
if selectedImage[indexPath.row] != UIImage(named: "empty-image"){
cell.contentView.isUserInteractionEnabled = true
}else{
cell.contentView.isUserInteractionEnabled = false
}
The UIImage(named: "empty-image") is the image that appears when a user doesn't have made any selection for his image. I wrote the above code in the cellForItemAt function. Also, I had to set imageView.frame equal to the original width and height of the image.
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.imageView.image!.size.width, height: cell.imageView.image!.size.height)
The above solution solved my problem. Hopefully, it will help someone.

Problems with complex UITableViewCell

I'm trying to implement a custom complex UITableViewCell. My data source is relatively simple, but I could have some multiple elements.
class Element: NSObject {
var id: String
var titles: [String]
var value: String
init(id: String, titles: [String], value: String) {
self.id = id
self.titles = titles
self.value = value
}
}
I have an array of elements [Element] and, as you can see, for each element titles could have multiple string values. I must use the following layouts:
My first approach was to implement a dynamic UITableViewCell, trying to add content inside self.contentView at runtime. Everything is working, but it's not so fine and as you can see, reusability is not handled in the right way. Lag is terrible.
import UIKit
class ElementTableCell: UITableViewCell {
var titles: [String]!
var value: String!
var width: CGFloat!
var titleViewWidth: CGFloat!
var cellHeight: Int!
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
func drawLayout() {
titleViewWidth = (width * 2)/3
cellHeight = 46 * titles.count
for i in 0 ..< titles.count {
let view = initTitleView(title: titles[i], width: titleViewWidth, yPosition: CGFloat(cellHeight * i))
self.contentView.addSubview(view)
}
self.contentView.addSubview(initButton())
}
func initTitleView(title: String, width: CGFloat, yPosition: CGFloat) -> UIView {
let titleView: UILabel = UILabel(frame:CGRect(x:0, y:Int(yPosition), width: Int(width), height: 45))
titleView.text = title
return titleView
}
func initButton(value: String) -> UIButton {
let button = UIButton(frame:CGRect(x: 0, y: 0, width: 70, height:34))
button.setTitle(value, for: .normal)
button.center.x = titleViewWidth + ((width * 1)/3)/2
button.center.y = CGFloat(cellHeight/2)
return priceButton
}
}
And the UITableView delegate method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ElementTableCell(style: .default, reuseIdentifier: "ElementTableCell")
cell.width = self.view.frame.size.width
cell.titles = elements[indexPath.row].titles
cel.value = elements[indexPath.row].value
cell.drawLayout()
return cell
}
Now I'm thinking about a total different approach, such as using a UITableView Section for each element in elements array and a UITableViewCell for each title in titles. It could work, but I'm concerned about the right button.
Do you have any suggestion or other approach to share?
I solved changing application UI logic in order to overcome the problem. Thank you all.
Here's some code you can play with. It should work just be creating a new UITableView in a Storyboard and assigning it to BoxedTableViewController in this file...
//
// BoxedTableViewController.swift
//
import UIKit
class BoxedCell: UITableViewCell {
var theStackView: UIStackView!
var containingView: UIView!
var theButton: UIButton!
var brdColor = UIColor(white: 0.7, alpha: 1.0)
// "spacer" view is just a 1-pt tall UIView used as a horizontal-line between labels
// when there is more than one title label
func getSpacer() -> UIView {
let newView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 1))
newView.backgroundColor = brdColor
newView.translatesAutoresizingMaskIntoConstraints = false
newView.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
return newView
}
// "label view" is a UIView containing on UILabel
// embedding the label in a view allows for convenient borders and insets
func getLabelView(text: String, position: Int) -> UIView {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
let newLabel = UILabel()
newLabel.font = UIFont.systemFont(ofSize: 15.0)
newLabel.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
newLabel.textColor = .black
newLabel.layer.borderWidth = 1
newLabel.layer.borderColor = brdColor.cgColor
newLabel.numberOfLines = 0
newLabel.text = text
newLabel.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(newLabel)
newLabel.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 8.0).isActive = true
newLabel.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -8.0).isActive = true
var iTop: CGFloat = 0.0
var iBot: CGFloat = 0.0
// the passed "position" tells me whether this label is:
// a Single Title only
// the first Title of more than one
// the last Title of more than one
// or a Title with a Title above and below
// so we can set up proper top/bottom padding
switch position {
case 0:
iTop = 16.0
iBot = 16.0
break
case 1:
iTop = 12.0
iBot = 8.0
break
case -1:
iTop = 8.0
iBot = 12.0
break
default:
iTop = 8.0
iBot = 8.0
break
}
newLabel.topAnchor.constraint(equalTo: v.topAnchor, constant: iTop).isActive = true
newLabel.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: -iBot).isActive = true
return v
}
func setupThisCell(rowNumber: Int) -> Void {
// if containingView is nil, it hasn't been created yet
// so, create it + Stack view + Button
// else
// don't create new ones
// This way, we don't keep adding more and more views to the cell on reuse
if containingView == nil {
containingView = UIView()
containingView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containingView)
containingView.layer.borderWidth = 1
containingView.layer.borderColor = brdColor.cgColor
containingView.backgroundColor = .white
containingView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
containingView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
containingView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6.0).isActive = true
containingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -6.0).isActive = true
theStackView = UIStackView()
theStackView.translatesAutoresizingMaskIntoConstraints = false
containingView.addSubview(theStackView)
theStackView.axis = .vertical
theStackView.spacing = 4.0
theStackView.alignment = .fill
theStackView.distribution = .fill
theButton = UIButton(type: .custom)
theButton.translatesAutoresizingMaskIntoConstraints = false
containingView.addSubview(theButton)
theButton.backgroundColor = .blue
theButton.setTitleColor(.white, for: .normal)
theButton.setTitle("The Button", for: .normal)
theButton.setContentHuggingPriority(1000, for: .horizontal)
theButton.centerYAnchor.constraint(equalTo: containingView.centerYAnchor, constant: 0.0).isActive = true
theButton.trailingAnchor.constraint(equalTo: containingView.trailingAnchor, constant: -8.0).isActive = true
theStackView.topAnchor.constraint(equalTo: containingView.topAnchor, constant: 0.0).isActive = true
theStackView.bottomAnchor.constraint(equalTo: containingView.bottomAnchor, constant: 0.0).isActive = true
theStackView.leadingAnchor.constraint(equalTo: containingView.leadingAnchor, constant: 0.0).isActive = true
theStackView.trailingAnchor.constraint(equalTo: theButton.leadingAnchor, constant: -8.0).isActive = true
}
// remove all previously added Title labels and spacer views
for v in theStackView.arrangedSubviews {
v.removeFromSuperview()
}
// setup 1 to 5 Titles
let n = rowNumber % 5 + 1
// create new Title Label views and, if needed, spacer views
// and add them to the Stack view
if n == 1 {
let aLabel = getLabelView(text: "Only one title for row: \(rowNumber)", position: 0)
theStackView.addArrangedSubview(aLabel)
} else {
for i in 1..<n {
let aLabel = getLabelView(text: "Title number \(i)\n for row: \(rowNumber)", position: i)
theStackView.addArrangedSubview(aLabel)
let aSpacer = getSpacer()
theStackView.addArrangedSubview(aSpacer)
}
let aLabel = getLabelView(text: "Title number \(n)\n for row: \(rowNumber)", position: -1)
theStackView.addArrangedSubview(aLabel)
}
}
}
class BoxedTableViewController: UITableViewController {
let cellID = "boxedCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(BoxedCell.self, forCellReuseIdentifier: cellID)
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1250
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! BoxedCell
// Configure the cell...
cell.setupThisCell(rowNumber: indexPath.row)
return cell
}
}
I'll check back if you run into any problems with it (gotta run, and haven't fully tested it yet -- and ran out of time to comment it - ugh).
You can also use tableview as tableviecell and adjust cell accordingly.
u need to layout cell in func layoutsubviews after set data to label and imageview;
Yes, split ElementTableCell to section with header and cells is much better approach. In this case you have no need to create constraints or dealing with complex manual layout. This would make your code simple and make scrolling smooth.
The button you use can be easily moved to the reusable header view
Is you still want to keep it in one complete cell, where is a way to draw manually the dynamic elements, such as titles and separators lines. Manually drawing is faster as usual. Or remove all views from cell.contentView each time you adding new. But this way is much more complicated.
Greate article about how to make UITableView appearence swmoth:
Perfect smooth scrolling in UITableViews

Swift 3 UITableView cell seperator disappears when i remove cell subviews

I have an array type of string arrays and I print it to tableView within for loop.I know its a bad practice loop inside cellForRowAt indexPath: function but I don't have any solution.My Problem is every time I move my tableview on simulator,i insert more subviews on existing ones.it overwrites older ones and to prevent i use
for view in cell.subviews {
view.removeFromSuperview()
}
but it deletes my cell seperators with my older cell labels.How can i remove only cell data not my seperator.
func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell {
var cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
//print(cell.subviews)
for view in cell.subviews {
view.removeFromSuperview()
}
for i in 0..<globalHeaderStructArray.count{
var newLabel = UILabel(frame: CGRect(x:xCoordination, y:10, width:Double(globalHeaderStructArray[i].width)!, height:30.0))
newLabel.font = UIFont(name: "Avenir", size: 17.0)
newLabel.textColor = UIColor.darkGray
newLabel.text = "\(globalDataArray[indexPath.row][i])"
var scrollview = UIScrollView(frame: cell.bounds)
scrollview.contentSize = CGSize(width:cell.bounds.width * 5, height:cell.bounds.height) // will be 5 times as wide as the cell
scrollview.isPagingEnabled = true
cell.contentView.addSubview(scrollview)
cell.addSubview(newLabel)
xCoordination += Double(globalHeaderStructArray[i].width)!
}
xCoordination = 0.0
return cell
}
You can set tag to your label and scrollView object and check that inside loop like this.
for view in cell.subviews {
if view.tag == 101 || view tag == 102 {
view.removeFromSuperview()
}
}
for i in 0..<globalHeaderStructArray.count{
var newLabel = UILabel(frame: CGRect(x:xCoordination, y:10, width:Double(globalHeaderStructArray[i].width)!, height:30.0))
newLabel.font = UIFont(name: "Avenir", size: 17.0)
newLabel.textColor = UIColor.darkGray
newLabel.text = "\(globalDataArray[indexPath.row][i])"
newLabel.tag = 101
var scrollview = UIScrollView(frame: cell.bounds)
scrollview.contentSize = CGSize(width:cell.bounds.width * 5, height:cell.bounds.height) // will be 5 times as wide as the cell
scrollview.isPagingEnabled = true
scrollview.tag = 102
cell.contentView.addSubview(scrollview)
cell.addSubview(newLabel)
xCoordination += Double(globalHeaderStructArray[i].width)!
}
Tips: It is batter if you use interface builder to design your cell instead of adding UI element at run time.

how can I display the following scenes hierarchy in swift using collectionView?

I have the following display scenes available. I am getting confused what type of hierarchy of controls I should take to display these type of view in xib .
please give ideas to show these types of scenes. because my items are coming dynamically . Its not fixed. so if I took tableview to display the first items and its categories then where should i display the rest items.
Edited
I took four sections. In 1st section collection and delivery buttons. In 3rd notes and in 4th allergy & checkout .
In 2nd my order items are there. but here I have two level of data.. order item name like chicken kabab small,... etc and 2nd level its addons like plain nan, bottle of drink,... etc. Here my order items is iterating in cell as well as my addons are iterating. I took the order items name in cell. now where should i take the addon items programatically and how to set the size of each cell based on its all contents inside it.
class cartVC: UIViewController ,UITableViewDataSource,UITableViewDelegate,UITextViewDelegate{
var tableData = ["al","dbd","gdge","kjdkas","al","dbd","gdge","kjdkas","al","dbd","gdge","kjdkas","al","dbd","gdge","kjdkas"]
var mainview = UIView()
#IBOutlet weak var cartTableView: UITableView!
#IBAction func backBtn(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func changeColor(sender:UISegmentedControl){
switch(sender.selectedSegmentIndex){
case 0:
print("collection clicked")
case 1:
print("delivery clicked")
default:
self.view.backgroundColor = UIColor.blueColor()
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 4
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rowcount = 0
if section == 0{
rowcount = 0
}
if section == 1 {
rowcount = tableData.count
}
if section == 2{
rowcount == 0
}
if section == 3{
rowcount == 0
}
return rowcount
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0{
let headerView = UIView()
//set the frame
let frame = UIScreen.mainScreen().bounds
// headerView.frame = CGRectMake(0, 0, tableView.frame.size.width, 60)
headerView.frame = CGRectMake(frame.minX , frame.minY, frame.width, 60)
headerView.backgroundColor = UIColor.whiteColor()
//Initialize segment control
let items = ["Collection","Delivery"]
let customSC = UISegmentedControl(items: items)
customSC.selectedSegmentIndex = 0
//set the frame amd segmented control
customSC.frame = CGRectMake(frame.minX + 10, frame.minY + 5, frame.width - 20, 30)
// style the segmented control
customSC.layer.cornerRadius = 5.0
customSC.backgroundColor = UIColor.clearColor()
customSC.tintColor = UIColor.redColor()
//add target action method
customSC.addTarget(self, action: #selector(CartViewController.changeColor(_:)), forControlEvents: .ValueChanged)
//add subview
headerView.addSubview(customSC)
//Add label
let headinglbl = UILabel(frame: CGRect(x: frame.minX + 10, y: frame.minY + 40, width: tableView.frame.size.width, height: 20))
headinglbl.text = "Your Order"
headinglbl.font = UIFont.boldSystemFontOfSize(17)
headinglbl.textColor = UIColor.blackColor()
headinglbl.textAlignment = .Center
headerView.addSubview(headinglbl)
mainview = headerView
}
if section == 2{
let totalView = UIView()
totalView.frame = CGRectMake(0, 0, tableView.frame.size.width, 60)
totalView.backgroundColor = UIColor.clearColor()
//Add discount label
let discount = 14.5
let discountlbl = UILabel(frame: CGRectMake(10, 0, tableView.frame.size.width, 20))
discountlbl.text = "Online Collection Discount(\(discount)%)"
discountlbl.font = UIFont.systemFontOfSize(14)
discountlbl.textColor = UIColor.darkGrayColor()
discountlbl.textAlignment = .Left
totalView.addSubview(discountlbl)
//Add discount price
let discountprice = UILabel(frame: CGRectMake(tableView.frame.size.width-60, 0, tableView.frame.size.width, 20))
discountprice.text = "£ 1.27"
discountprice.font = UIFont.systemFontOfSize(14)
discountprice.textColor = UIColor.blackColor()
discountprice.textAlignment = .Left
totalView.addSubview(discountprice)
//Add label
let lbl = UILabel(frame: CGRectMake(10, 20, tableView.frame.size.width, 40))
lbl.text = "Total"
lbl.font = UIFont.boldSystemFontOfSize(20)
lbl.textColor = UIColor.blackColor()
lbl.textAlignment = .Left
totalView.addSubview(lbl)
//calculate amount label
let totalAmountLbl = UILabel(frame: CGRectMake(totalView.frame.width-70, 20, totalView.frame.width, 40))
totalAmountLbl.text = "£ 0.0"
totalAmountLbl.font = UIFont.boldSystemFontOfSize(20)
totalAmountLbl.textColor = UIColor.blackColor()
totalAmountLbl.textAlignment = .Left
totalView.addSubview(totalAmountLbl)
mainview = totalView
}
if section == 3{
let footerView = UIView()
footerView.frame = CGRectMake(0, 0, tableView.frame.size.width, 200)
footerView.backgroundColor = UIColor.clearColor()
//Add note label
let notelbl = UILabel(frame: CGRectMake(10, 10, tableView.frame.size.width, 20))
notelbl.text = "Leave a note"
notelbl.font = UIFont.boldSystemFontOfSize(17)
notelbl.textColor = UIColor.blackColor()
notelbl.textAlignment = .Left
footerView.addSubview(notelbl)
//Add a note textview
let noteTxt = UITextView()
noteTxt.frame = CGRectMake(10, 40, footerView.frame.width-20, 50)
noteTxt.backgroundColor = UIColor.lightGrayColor()
noteTxt.keyboardType = UIKeyboardType.Default
noteTxt.text = "e.g. Instructions about yout order"
noteTxt.textColor = UIColor.blackColor()
noteTxt.delegate = self
footerView.addSubview(noteTxt)
// Add allergy button
let allergyBtn = UIButton(type:.System)
allergyBtn.frame = CGRectMake(50, 100, 200, 20)
allergyBtn.setTitle("Do You have any allergy ?", forState: .Normal)
allergyBtn.setTitleColor(UIColor.redColor(), forState: .Normal)
allergyBtn.titleLabel?.font = UIFont(name: "", size: 10)
footerView.addSubview(allergyBtn)
// Add checkout button
let checkoutBtn = UIButton(type:.System)
checkoutBtn.frame = CGRectMake(100, 140, 100, 40)
checkoutBtn.setTitle("Check out", forState: .Normal)
checkoutBtn.setTitleColor(UIColor.whiteColor(), forState: .Normal)
checkoutBtn.titleLabel?.font = UIFont(name: "", size: 10)
checkoutBtn.backgroundColor = UIColor.redColor()
checkoutBtn.layer.cornerRadius = 5
footerView.addSubview(checkoutBtn)
mainview = footerView
}
return mainview
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cartcell")! as! CartTableViewCell
cell.itemLabel.text = tableData[indexPath.row]
return cell
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
var heightCount:CGFloat = 0
if section == 0{
heightCount = 60.0
}
if section == 2{
heightCount = 60.0
}
if section == 3{
heightCount = 200.0
}
return heightCount
}
My customcell code
import UIKit
class CartTableViewCell: UITableViewCell {
let padding: CGFloat = 5
var background: UIView!
var itemLabel: UILabel!
var priceLabel: UILabel!
var deleteBtn:UIButton!
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = UIColor.clearColor()
selectionStyle = .None
background = UIView(frame: CGRectZero)
background.alpha = 0.6
contentView.addSubview(background)
deleteBtn = UIButton(frame: CGRectZero)
deleteBtn.setImage(UIImage(named: "deleteBin.png"), forState: .Normal)
contentView.addSubview(deleteBtn)
itemLabel = UILabel(frame: CGRectZero)
itemLabel.textAlignment = .Left
itemLabel.textColor = UIColor.whiteColor()
contentView.addSubview(itemLabel)
priceLabel = UILabel(frame: CGRectZero)
priceLabel.textAlignment = .Center
priceLabel.textColor = UIColor.whiteColor()
contentView.addSubview(priceLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
background.frame = CGRectMake(0, padding, frame.width, frame.height-2 * padding)
deleteBtn.frame = CGRectMake(padding, (frame.height - 25)/2, 40, 25)
priceLabel.frame = CGRectMake(frame.width-100, padding, 100, frame.height - 2 * padding)
itemLabel.frame = CGRectMake(CGRectGetMaxX(deleteBtn.frame) + 10, 0, frame.width - priceLabel.frame.width - CGRectGetMaxX(deleteBtn.frame) + 10, frame.height)
}
}
As our mates already said about using tableview and sections, Here we gonna follow the same way.Since it is a broad topic to explain i'll give some hint and at last you can find link for demo project.
First add a tableview in your storyboard then add collection,Delivery & Your order objects as tableview header
Create a new class subclass of UITableviewcell with xib let's name it as Cell1.Now add delete icon, main dish label and price label,for sub items we gonna use another UITableview.
Now create another UITableviewcell with xib name it as Cell2, prepare that xib for sub items and their price.
In cell1 numberOfSectionsInTableView return number of main dish count and in numberOfRowsInSection return 1, Now load name of all main dishes in their respective label's
Upto now we having some number of section(depending on number of main items) each section having one UITableview.
Now we have to change height of tableview cell dynamically depending on SubItems count. so in heightForRowAtIndexPath i have added following lines
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let subItems = tableContainer![indexPath.section].valueForKey("additional_items") as! NSArray
var defaultCellHeight:CGFloat = 37//Consider 37 as height of cell without subitems
//Following for loop keeps on increasing defaultCellHeight depending on available count
for _ in subItems {
defaultCellHeight += 37
}
return defaultCellHeight + 20
}
Since it is hard to explain everything deeply i have provide code for heightForRowAtIndexPath.While looking into the demo project you'll understand everything
NOTE : Upto now we have loaded all main dishes details, and we have provided enough room for upcoming sub item's.
In Cell1 class add tableview delegate and datasource in awakeFromNib,add all datasource methods as required.set numberOfSectionsInTableView as 1 and numberOfRowsInSection as subitem count
That's it we have loaded tableview dynamically as per your requirement.
Now at last add discount, total, leave a note& Checkout objects in separate tableviewcell class an load it at last index.
Or add add all those objects inside a UIView and add it as Main tableview's footer.
NOTE : The above hints are just for reference, For better clarification of concept i have added a demo project's github repo.
RESULT :

How to set custom margins for a UITableViewCell's .selectedBackgroundView

I'm using a table view to display a tree structure. Each cell corresponds to a node that the user can expand or collapse. The level of each node is visualized by having increasingly large indents at the leading edge of the cells. Those indents are set by using layoutMargins. This seems to work well for the cell's label and separators. Here's some code:
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let cellLevel = cellLevelForIndexPath(indexPath)
let insets: UIEdgeInsets = UIEdgeInsetsMake(0.0, CGFloat(cellLevel) * 20.0, 0.0, 0.0)
cell.separatorInset = insets
cell.layoutMargins = insets
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cellId") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cellId")
}
cell!.preservesSuperviewLayoutMargins = false
cell!.backgroundColor = UIColor.clearColor()
let cellLevel = cellLevelForIndexPath(indexPath)
if let textlabel = cell!.textLabel {
textlabel.text = "Cell # level \(cellLevel)"
textlabel.textColor = UIColor.blackColor()
}
cell!.selectedBackgroundView = UIView(frame: CGRectZero)
cell!.selectedBackgroundView.backgroundColor = UIColor.cyanColor()
return cell!
}
The resulting table looks like this:
The question I'm facing now is this: how can I elegantly apply the same indent to the cell's .selectedBackgroundView, so that it appears flush with the text and separator line? The end result should look something like this:
Note: I'm currently achieving the desired effect by making the .selectedBackgroundView more complex and adding background-colored subviews of varying size that effectively mask parts of the cell, e.g. like this:
let maskView = UIView(frame: CGRectMake(0.0, 0.0, CGFloat(cellLevel) * 20.0, cell!.bounds.height))
maskView.backgroundColor = tableView.backgroundColor
cell!.selectedBackgroundView.addSubview(maskView)
But I strongly feel that there must be a nicer way to do this.
Figured out a way to make it work. The trick for me was to stop thinking about the .selectedBackgroundView as the visible highlight itself (and thus trying to mask or resize it) and to treat it more like a canvas instead.
Here's what I ended up doing. First a more convenient way to get the appropriate inset for each level:
let tableLevelBaseInset = UIEdgeInsetsMake(0.0, 20.0, 0.0, 0.0)
private func cellIndentForLevel(cellLevel: Int) -> CGFloat {
return CGFloat(cellLevel) * tableLevelBaseInset.left
}
And then in the cellForRowAtIndexPath:
cell!.selectedBackgroundView = UIView(frame: CGRectZero)
cell!.selectedBackgroundView.backgroundColor = UIColor.clearColor()
let highlight = UIView(frame: CGRectOffset(cell!.bounds, cellIndentForLevel(cellLevel), 0.0))
highlight.backgroundColor = UIColor.cyanColor()
cell!.selectedBackgroundView.addSubview(highlight)
Seems to work nicely.
var selectedView = UIView()
override func awakeFromNib() {
super.awakeFromNib()
self.selectedBackgroundView = {
let view = UIView()
view.backgroundColor = UIColor.clear
selectedView.translatesAutoresizingMaskIntoConstraints = false
selectedView.backgroundColor = .hetro_systemGray6
selectedView.roundAllCorners(radius: 8)
view.addSubview(selectedView)
selectedView.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
selectedView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -5).isActive = true
selectedView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
selectedView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
return view
}()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
selectedBackgroundView?.isHidden = false
} else {
selectedBackgroundView?.isHidden = true
}
}

Resources