Custom cell with dynamic subviews stack - ios

I have a tableView populated by an array of 'Post'. On this tableView I register a class 'PostCell'. This PostCell isn't supported by any nib since I want to dynamically compose it with subviews.
Suppose two subviews, a top yellowView and a bottom redView, each subclass of UIView, are stacked on a PostCell.
My issue is that the below code returns this:
ViewController
override func viewDidLoad(){
super.viewDidLoad()
tableView.delegate = self
tableView.datasource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
tableView.registerClass(PostCell.self, forCellReuseIdentifier: "PostCell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = posts[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell") as! PostCell
cell.configureForData(post)
return cell
}
PostCell
class PostCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fun configureForData(post: Post){
let yellowView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 40))
yellowView.backgroundColor = UIColor.yellowColor()
yellowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(yellowView)
let redView: UIView = UIView(frame: CGRect(x: 0, y: 40, width: contentView.frame.width, height: 40))
redView.backgroundColor = UIColor.redColor()
redView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(redView)
}
override func layoutSubviews() {
super.layoutSubviews()
let height = NSLayoutConstraint(item: contentView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 80)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(height)
}
}
EDIT 1: This fixes the layout
class PostCell: UITableViewCell {
let yellowView = UIView()
let redView = UIView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fun configureForData(post: Post){
yellowView.backgroundColor = UIColor.yellowColor()
yellowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(yellowView)
redView.backgroundColor = UIColor.redColor()
redView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(redView)
layoutIfNeeded
}
override func layoutSubviews() {
super.layoutSubviews()
let height = NSLayoutConstraint(item: contentView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 80)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(height)
yellowView.frame = UIView(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 40))
redView.frame = UIView(frame: CGRect(x: 0, y: 40, width: contentView.frame.width, height: 40))
}

Try to use cell.layoutIfNeeded() after cell.configureForData(post) in your tableView:cellForRowAtIndexPath function. But I think it's better to calculate cell height and return it in tableView:heightForRowAtIndexPath function.
Also you have problem with cell width:
It's because you got contentView.frame.width before id correctly laid out. You need to update frames in each layoutSubviews call or use constraints.

Related

Swift Custom UITextField Not Editable

I Have a Custom UITextField in my app,
class UnderLinedText: UITextField {
override func layoutSubviews() {
super.layoutSubviews()
let border = CALayer()
let width = CGFloat(1.0)
border.borderColor = UIColor(red:0.21, green:0.13, blue:0.34, alpha:1.00).cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
override init(frame: CGRect) {
super.init(frame: frame)
self.textAlignment = .center
self.borderStyle = .none
self.isEnabled = true
self.isUserInteractionEnabled = true
self.font = UIFont(name: "HelveticaNeueLT-Regular", size: 20)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And I added it to my view controllers but its not working (cant edit it text)
class CommercialOrder: UIViewController , UITextFieldDelegate{
let bid = UnderLinedText()
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
bid.delegate = self
}
func updateUI(){
self.view.backgroundColor = .white
self.view.addSubview(scrollView)
scrollView.addSubview(contentView)
scrollView.showsVerticalScrollIndicator = false
scrollView.autoPinEdgesToSuperviewSafeArea()
contentView.autoPinEdgesToSuperviewEdges()
contentView.autoMatch(.width, to: .width, of: view)
contentView.addSubview(header)
contentView.addSubview(bid_title)
bid.autoPinEdge(.top, to: .bottom, of: bid_title, withOffset: 10)
bid.autoPinEdge(toSuperviewEdge: .trailing, withInset: 20)
bid.autoPinEdge(toSuperviewEdge: .leading, withInset: 20)
bid.autoSetDimension(.height, toSize: 50)
bid.text = "Reciever mail"
}
I even added the UITextFieldDelegate make isEnabled and isUserInteractionEnabled set to true bit its still not working
Any help will be much appreciated
Hope to help someone, my issue that the contentView has no Height which will effect on the scrolling (view not scrollable) so it will not get touch events just add the height or assign constraint to the bottom of last view to the contentView like in my case button
button.autoPinEdge(toSuperviewEdge: .bottom, withInset: 30)
And thats it

How to fix wrong indexPath returned by didSelectRowAt?

I have a UITableView; without a tableHeaderView tapping a row and triggering didSelectRowAt returns the correct index path.
When I set the tableHeaderView property, didSelectRowAt either does not fire or returns the tappedRow + 2. Where am I going wrong?
Here is my code
class MenuController: UIViewController {
// Mark -- Properties
var tableView: UITableView!
var delegate: HomeControllerDelegate?
var headerView: HeaderView? = nil
var user: User? = nil
// Mark -- Init
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
if let user = self.user {
populateMenuHeader(email: user.email, firstName: user.firstName, lastName: user.lastName, imageUrl: user.imageUrl)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//updateHeaderViewHeight(for: tableView.tableHeaderView)
}
func updateHeaderViewHeight(for header: UIView?) {
guard let headerView = headerView else { return }
headerView.frame.size.height = 170
}
func populateMenuHeader(email: String, firstName: String, lastName: String, imageUrl: String) {
headerView?.emailLabel?.text = email
headerView?.nameLabel?.text = "\(firstName) \(lastName)"
let request = ImageRequest(
url: URL(string: imageUrl)!,
processors: [
ImageProcessor.Resize(size: CGSize(width: 70, height: 70)),
ImageProcessor.Circle()
]
)
Nuke.loadImage(with: request, into: headerView!.imageView!)
}
// Mark -- Handlers
func configureTableView() {
// Create Material Header
headerView = HeaderView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 170))
//headerView?.heightAnchor.constraint(equalToConstant: 170).isActive = true
headerView?.translatesAutoresizingMaskIntoConstraints = false
tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.tableHeaderView = headerView
tableView.sectionHeaderHeight = 170
tableView.register(MenuOptionCell.self, forCellReuseIdentifier: reuseIdentifier)
tableView.backgroundColor = .darkGray
tableView.separatorStyle = .none
tableView.rowHeight = 80
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
}
extension MenuController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! MenuOptionCell
let menuOption = MenuOption(rawValue: indexPath.row)
cell.descriptionLabel.text = menuOption?.description
cell.iconImageView.image = menuOption?.image
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
print("tapped row: \(row)")
let menuOption = MenuOption(rawValue: row)
delegate?.handleMenuToggle(forMenuOption: menuOption)
}
}
class CustomView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
if let context = UIGraphicsGetCurrentContext() {
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(1)
context.move(to: CGPoint(x: 0, y: bounds.height))
context.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
context.strokePath()
}
}
}
class HeaderView : UIView {
var imageView: UIImageView? = nil
var nameLabel: UILabel? = nil
var emailLabel: UILabel? = nil
override init(frame: CGRect) {
super.init(frame: frame)
imageView = UIImageView()
imageView?.translatesAutoresizingMaskIntoConstraints = false
nameLabel = UILabel()
nameLabel?.translatesAutoresizingMaskIntoConstraints = false
nameLabel?.font = UIFont(name: "Avenir-Light", size: 20)
nameLabel?.text = "Test name"
nameLabel?.textColor = .white
emailLabel = UILabel()
emailLabel?.translatesAutoresizingMaskIntoConstraints = false
emailLabel?.textColor = .white
emailLabel?.font = UIFont(name: "Avenir-Light", size: 15)
emailLabel?.text = "testemail#gmail.com"
self.addSubview(imageView!)
self.addSubview(nameLabel!)
self.addSubview(emailLabel!)
let lineView = CustomView(frame: CGRect(x: 0, y: frame.height - 1, width: frame.width, height: 1))
self.addSubview(lineView)
imageView?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
imageView?.topAnchor.constraint(equalTo: topAnchor, constant: 20).isActive = true
imageView?.widthAnchor.constraint(equalTo: widthAnchor, constant: 70).isActive = true
imageView?.heightAnchor.constraint(equalTo: heightAnchor, constant: 70).isActive = true
nameLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
nameLabel?.topAnchor.constraint(equalTo: imageView!.bottomAnchor, constant: 10).isActive = true
emailLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
emailLabel?.topAnchor.constraint(equalTo: nameLabel!.bottomAnchor, constant: 5).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem appears to be due to the fact that you are not properly setting the height of the header view. The documentation for tableHeaderView states:
When assigning a view to this property, set the height of that view to a nonzero value. The table view respects only the height of your view's frame rectangle; it adjusts the width of your header view automatically to match the table view's width.
Update your header view code:
Change:
headerView = HeaderView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 170))
//headerView?.heightAnchor.constraint(equalToConstant: 170).isActive = true
headerView?.translatesAutoresizingMaskIntoConstraints = false
...
tableView.sectionHeaderHeight = 170
to just:
headerView = HeaderView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 170))
That's it. No need to mess with constraints. No need to set the unrelated section height. Just give the header view's frame the desired height.

Swift 4: Could not load NIB in bundle: 'NSBundle' for custom UITableViewCell (no IB)

I have built my own UITableViewCell (entirely by code, no interface builder) and I'm getting the fallowing exception:
[...] NSInternalInconsistencyException', reason: 'Could not load NIB in
bundle: 'NSBundle [...]
Im fairly certain I don't need to use awakeFromNib() (see code) but this is the only way I got it working when passing it as "non-reusable"
// this works
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath [...] {
return myCustomCell()
// this fails
tableView.register(MyCustomCell.self, forCellReuseIdentifier: "foo")
//throws immediately after this line: "[...] Could not load NIB in bundle"
My custom cell:
class TextValueTableViewCell: UITableViewCell {
let textField: UITextField = UITextField(
frame: CGRect(x: 30, y: 5, width: 350, height: 25)
)
let label = UILabel(frame: CGRect(x: 30, y: 5, width: 350, height: 25))
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.awakeFromNib();
}
override func awakeFromNib() {
super.awakeFromNib()
self.label.text = "oh"
self.contentView.addSubview(self.label)
self.contentView.addSubview(self.textField)
self.textField.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -25).isActive = true
self.textField.translatesAutoresizingMaskIntoConstraints = false;
self.textField.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor, constant: 0).isActive = true
self.textField.widthAnchor.constraint(equalTo: self.label.widthAnchor, constant: 0).isActive = true
self.textField.textAlignment = .right;
self.textField.placeholder = "Account Name"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
}
As I said, I have not used the interface builder (and I'm not planing on it) so I'm not sure why it would try to load something from the bundle. Specifically, wouldn't I have to register it as a NIB then?
tableView.register(<#T##nib: UINib?##UINib?#>, forCellReuseIdentifier: <#T##String#>)
class TextValueTableViewCell: UITableViewCell {
let textField: UITextField = UITextField(
frame: CGRect(x: 30, y: 5, width: 350, height: 25)
)
let label = UILabel(frame: CGRect(x: 30, y: 5, width: 350, height: 25))
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setupCell()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}
func setupCell() {
label.text = "oh"
contentView.addSubview(label)
contentView.addSubview(textField)
textField.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -25).isActive = true
textField.translatesAutoresizingMaskIntoConstraints = false;
textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0).isActive = true
textField.widthAnchor.constraint(equalTo: label.widthAnchor, constant: 0).isActive = true
textField.textAlignment = .right;
textField.placeholder = "Account Name"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
For example If your using a UIViewController class name as TextPicker
Then u need to use like Bellow Code
let bundle = Bundle(for: TextPicker.self)
super.init(nibName: "TextPicker" , bundle: bundle)
If You are using TableView with Xib in other project as Framework
tableView.register(UINib.init(nibName: "TextPickerCell", bundle: bundle), forCellReuseIdentifier: "TextPickerCell")
let bundle = Bundle(for: TextPicker.self)

UITableViewCell subclassed in swift 3 has contentView always of 320 points

I am trying to create a UILabel in my subclassed UITableViewCell to make it the full width of the cell. But the contentView is always 320pt if in iPhone 7 or 7+.
The code is:
import UIKit
import SwipeCellKit
class ReuseableCell: SwipeTableViewCell
{
public var test = UILabel();
var animator: Any?
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier);
// Always 320pt and I want full width of the table cell
var width = self.contentView.frame.width;
test = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: 100));
test.text = "Lorem ipsum...";
...
I have been stuck on this for 2 nights. Thanks.
You can try auto-layout
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier);
test = UILabel(frame: CGRect.zero);
self.contentView.addSubview(test)
test.translatesAutoresizingMaskIntoConstraints = false
test.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
test.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
test.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
test.heightAnchor.constraint(equalToConstant: 200).isActive = true
test.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
You have to add Your label inside override init(style: UITableViewCellStyle, reuseIdentifier: String!) but to read correct frame you have override override func layoutSubviews() and read correct frame
May be you forgot to set constrains to your 'UITableView'. Try set tableview width constrain equal to superview width.
In ViewController:
self.view.addConstraint(NSLayoutConstraint(item: self.tableView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: 0))

Images don't load on button. Swift

I made a button with the class WWZButton but the class does not work apparently. i want to add a image what is 'arrow_btn'
This is how it looks now. But there needs to become the arrow_btn on the right side. Login button
this is the class code: `import UIKit
class WWZButton: UIButton {
#IBInspectable var isOrange: Bool = true
private var rightImageView: UIImageView?
func setupView() {
rightImageView?.removeFromSuperview()
rightImageView = UIImageView(frame: CGRect(x: frame.width - 35, y: 7.5, width: 25, height: 15), image: UIImage(named: "arrow_btn")!)
rightImageView?.contentMode = .ScaleAspectFit
addSubview(rightImageView!)
contentHorizontalAlignment = .Left
contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
setTitleColor(UIColor.whiteColor(), forState: .Normal)
Utilities.createBorder(forView: self, color: UIColor.clearColor(), width: 0, radius: 8)
if isOrange == true {
backgroundColor = UIColor.orangeButtonColor()
} else {
backgroundColor = UIColor.grayButtonColor()
}
let heightConstraint = NSLayoutConstraint(item: self,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1,
constant: 30)
self.addConstraint(heightConstraint)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
}
Screenshot of button class + isOrange set true
Why is it not working? The code is good?
This works with me
class WWZButton: UIButton {
#IBInspectable var isOrange: Bool = true
private var rightImageView: UIImageView?
func setupView() {
rightImageView?.removeFromSuperview()
rightImageView?.contentMode = .scaleAspectFit
rightImageView = UIImageView(frame: CGRect(x: frame.width - 35, y: 7.5, width: 40, height: 40))
rightImageView?.image = UIImage.init(named: "car1.png")
rightImageView?.contentMode = .scaleAspectFit
addSubview(rightImageView!)
contentHorizontalAlignment = .left
contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
setTitleColor(UIColor.white, for: .normal)
if isOrange == true {
backgroundColor = UIColor.green
} else {
backgroundColor = UIColor.blue
}
let heightConstraint = NSLayoutConstraint(item: self,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: 100)
self.addConstraint(heightConstraint)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
}

Resources