add individual amount of buttons to cell programmatically - ios

I want to build the following programmatically:
but I want to add the custom buttons programmatically. Here I have no idea, how the tell the buttons to respect the space between them and how to resize the height of the cell if the next button didn't fits into the same "row".
i tried with constraints:
import Material
class ChipButton: Button {
override func prepare() {
super.prepare()
cornerRadiusPreset = .cornerRadius5
backgroundColor = UIColor.lightGray
titleColor = Color.darkText.primary
pulseAnimation = .none
contentEdgeInsets = EdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
isUserInteractionEnabled = false
titleLabel?.font = RobotoFont.regular
isOpaque = true
let constraintTop = NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: superview, attribute: .top, multiplier: 1, constant: 4)
let constraintLeading = NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: superview, attribute: .leading, multiplier: 1, constant: 4)
superview?.addConstraint(constraintTop)
superview?.addConstraint(constraintLeading)
}
}
and I add the buttons like the following:
for tag in item.tags {
let chip = ChipButton()
chip.title = tag.text
cell!.layout(chip).edges(top: 4, left: 4, bottom: 4, right: 4)
}
but the declaration of constrainTop and constrainLeading throws an error and without the constraints the buttons r on top of each other the and the size of the buttons r false.

The comment of #Palpatim inspired to do it like the following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell?
if let item: TagItem = items[indexPath.section][indexPath.row] as? TagItem {
if (item.tags.count > 0) {
// show all tags as a Chip
cell = TableViewCell()
cell?.isUserInteractionEnabled = false
var hStackView = UIStackView()
hStackView.axis = .horizontal
hStackView.spacing = 8
hStackView.alignment = .fill
hStackView.distribution = .fill
let vStackView = UIStackView()
vStackView.axis = .vertical
vStackView.spacing = 8
vStackView.alignment = .top
var tagsWidth: CGFloat = 0
for tag in item.tags {
let chip = ChipButton()
chip.title = tag.text
chip.sizeToFit()
if (tagsWidth + chip.bounds.width < (cell?.bounds.width)!) {
tagsWidth += chip.bounds.width
hStackView.addArrangedSubview(chip)
}
else {
vStackView.addArrangedSubview(hStackView)
tagsWidth = chip.bounds.width
hStackView = UIStackView()
hStackView.axis = .horizontal
hStackView.spacing = 8
hStackView.alignment = .fill
hStackView.distribution = .fill
hStackView.addArrangedSubview(chip)
}
}
vStackView.addArrangedSubview(hStackView)
cell!.layout(vStackView).edges(left: 16, right: 16).centerVertically()
return cell!
}
else {
cell = TableViewCell(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 40))
// show a label
let infoLabel = UILabel()
infoLabel.text = "no tags"
cell!.layout(infoLabel).centerVertically().edges(left: 16)
return cell!
}
}
cell = TableViewCell()
return cell!
}
and the result is:

Related

Self sizing cell with multiple Stack Views

I've looked all over the forum and attempted all the solutions and thus far nothing has worked. I noticed my UIImageView was overlaying multiple cells, meaning the celll did not automatically adjust its height. Here is the constraint i found in the console it complained about.
"<NSLayoutConstraint:0x600001970f50 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f86a4813dd0.height == 44 (active)>"
In my tableViewController I have the follow
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 300
Here is my entire cell that should self size.
import UIKit
class UserConnectionCell: UITableViewCell {
fileprivate let leftImageView: UIImageView = {
let uiImageView = UIImageView()
uiImageView.translatesAutoresizingMaskIntoConstraints = false
return uiImageView
}()
fileprivate let leftLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let middleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont(name: "Ariel", size: 10)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let rightImageView: UIImageView = {
let uiImageView = UIImageView()
uiImageView.translatesAutoresizingMaskIntoConstraints = false
return uiImageView
}()
fileprivate let rightLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let stackViewLeft: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
fileprivate let stackViewRight: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
fileprivate let stackViewMain: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.spacing = 0
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
//
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier:reuseIdentifier)
stackViewLeft.addArrangedSubview(leftImageView)
stackViewLeft.addArrangedSubview(leftLabel)
stackViewRight.addArrangedSubview(rightImageView)
stackViewRight.addArrangedSubview(rightLabel)
stackViewMain.addArrangedSubview(stackViewLeft)
stackViewMain.addArrangedSubview(middleLabel)
stackViewMain.addArrangedSubview(stackViewRight)
contentView.addSubview(stackViewMain)
}
// called when trying to layout subviews.
override func layoutSubviews() {
super.layoutSubviews()
stackViewLeft.addConstraint(NSLayoutConstraint(item: leftImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 100))
stackViewLeft.addConstraint(NSLayoutConstraint(item: leftImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 100))
stackViewRight.addConstraint(NSLayoutConstraint(item: rightImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 100))
stackViewRight.addConstraint(NSLayoutConstraint(item: rightImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 100))
NSLayoutConstraint.activate(
[stackViewMain.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0),
stackViewMain.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0),
stackViewMain.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0),
stackViewMain.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var viewModel : UserConnectionViewModel? {
didSet {
// move this to the view model
if let profileUrl = viewModel?.leftImageUrl {
leftImageView.loadImageFromURL(url: profileUrl)
} else {
leftImageView.image = UIImage(named: "defaultprofile")
}
if let profileUrl = viewModel?.rightImageUrl {
rightImageView.loadImageFromURL(url: profileUrl)
} else {
rightImageView.image = UIImage(named: "defaultprofile")
}
leftLabel.text = viewModel?.leftLabel
middleLabel.text = viewModel?.middleLabel
rightLabel.text = viewModel?.rightlabel
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask = .flexibleHeight
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Any ideas for why the cell is not self sizing?
First, a cell's contentView is a "special" view with properties integral to the table view's operation.
So, do not do this:
self.contentView.autoresizingMask = .flexibleHeight
Second, layoutSubviews() can be (and usually is) called multiple times during the lifecycle of a cell / view. Your constraint setup should be done in init:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier:reuseIdentifier)
stackViewLeft.addArrangedSubview(leftImageView)
stackViewLeft.addArrangedSubview(leftLabel)
stackViewRight.addArrangedSubview(rightImageView)
stackViewRight.addArrangedSubview(rightLabel)
stackViewMain.addArrangedSubview(stackViewLeft)
stackViewMain.addArrangedSubview(middleLabel)
stackViewMain.addArrangedSubview(stackViewRight)
contentView.addSubview(stackViewMain)
NSLayoutConstraint.activate([
// constrain main stack view to all 4 sides of contentView
stackViewMain.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0),
stackViewMain.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0),
stackViewMain.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0),
stackViewMain.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
// constrain left image view Width: 100-pts,
// Height equal to Width (1:1 ratio)
leftImageView.widthAnchor.constraint(equalToConstant: 100.0),
leftImageView.heightAnchor.constraint(equalTo: leftImageView.widthAnchor),
// constrain right image view Width: 100-pts,
// Height equal to Width (1:1 ratio)
rightImageView.widthAnchor.constraint(equalToConstant: 100.0),
rightImageView.heightAnchor.constraint(equalTo: rightImageView.widthAnchor),
])
}
So... replace your init with the above code and completely remove both your awakeFromNib() and layoutSubviews() funcs.
You should get this:

Empty UITableView with tableFooterView

I have a UITableView in which I want to show a message when the dataSource is empty. I do this with the well-known method of setting the backgroundView using the following extension:
extension UITableView {
func setEmptyMessage(_ message: String, _ image: String) {
let emptyView: UIView = {
let emptyView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
return emptyView
}()
let contentView: UIView = {
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
return contentView
}()
let messageLabel = UILabel()
let messageCommentStyle = NSMutableParagraphStyle()
messageCommentStyle.lineHeightMultiple = 1.2
let attributedString = NSMutableAttributedString(string: message)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: lightFeedUserNameFontColor, range: NSRange(location: 0, length: attributedString.length))
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: messageCommentStyle, range: NSRange(location: 0, length: attributedString.length))
attributedString.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: normalFontSize), range: NSRange(location: 0, length: attributedString.length))
messageLabel.attributedText = attributedString
messageLabel.numberOfLines = 0
messageLabel.font = UIFont.systemFont(ofSize: normalFontSize)
messageLabel.textAlignment = .center
messageLabel.sizeToFit()
messageLabel.translatesAutoresizingMaskIntoConstraints = false
let errorImage: UIImageView = {
let errorImage = UIImageView()
errorImage.translatesAutoresizingMaskIntoConstraints = false
return errorImage
}()
self.backgroundView = emptyView
emptyView.addSubview(contentView)
contentView.addSubview(errorImage)
contentView.addSubview(messageLabel)
contentView.centerYAnchor.constraint(equalTo: emptyView.centerYAnchor).isActive = true
contentView.centerXAnchor.constraint(equalTo: emptyView.centerXAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: emptyView.leadingAnchor, constant: normalSpacing * 3).isActive = true
contentView.trailingAnchor.constraint(equalTo: emptyView.trailingAnchor, constant: -(normalSpacing * 3)).isActive = true
contentView.topAnchor.constraint(equalTo: errorImage.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor).isActive = true
messageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
messageLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
messageLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
func restore() {
self.backgroundView = nil
}
}
And I set it like this:
if(tableData.isEmpty) {
self.tableView.setEmptyMessage("No results!", "none")
} else {
self.tableView.restore()
}
self.tableView.reloadData()
No big deal, we all have seen this and probably used it. And it works great. However, I now have a UIButton on the bottom placed in the tableFooterView. This button stays on top of the UITableView because it automatically positions itself right under the last cell, which is precisely what I want when there is data, but now the empty message is shown in the middle of the screen while the button is above it. How can I fix this so there is a sort of frame when the dataSource is empty?
To illustrate:
So now you are setting your tableView backgroundView to your emptyView. You can take a look on your dataSource and if it's empty - just return new cell for empty state with this message.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count > 0 ? dataSource.count : 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if dataSource.count.isEmpty 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "EmptyCell")
cell.textLabel.text = "No Results!"
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "SomeCell")
return cell
}
}
Write an extension
extension UITableView
{
func addErrorMessageLabel(noDataText:String = "No data available")->UILabel
{
let noDatalabel:UILabel!
noDatalabel=UILabel(frame: CGRect(x: self.frame.size.width/2-200, y: self.frame.size.height/2-50, width: 400, height: 100))
noDatalabel.textColor = textThemeColor
noDatalabel.text=noDataText
noDatalabel.numberOfLines=0
noDatalabel.textAlignment = .center
noDatalabel.isHidden=true
self.addSubview(noDatalabel)
self.alignCenterToSuperView(item: noDatalabel, horizentally: true, vertically: true,height: 100,width: 400)
return noDatalabel
}
func alignCenterToSuperView(item:UIView,horizentally:Bool,vertically:Bool , height:Int, width:Int)
{
if horizentally
{
item.translatesAutoresizingMaskIntoConstraints = false
let xConstraint = NSLayoutConstraint(item: item, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([xConstraint])
}
if vertically
{
item.translatesAutoresizingMaskIntoConstraints = false
let yConstraint = NSLayoutConstraint(item: item, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([yConstraint])
}
let Height = NSLayoutConstraint(item: item, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant:CGFloat(height))
item.addConstraint(Height)
let Width = NSLayoutConstraint(item: item, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant:CGFloat(width))
item.addConstraints([Width,Height])
}
}
In your ViewController create
var noDataLabel:UILabel!
In your ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
noDataLabel = tableView.addErrorMessageLabel()
noDataLabel.text = "No data found" // or your message
}
And final step
if(tableData.isEmpty) {
noDataLabel.isHideen = false
} else {
noDataLabel.isHideen = true
}

Why do we need upcasting cell obtained by dequeueReusableCell func?

I am very confused about fact that in
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MessageCell
if let message = messages?[indexPath.item] {
cell.message = message
}
cell.timeLibel
return cell
}
i supposed to upcast cell to a class implied by reuseIdentifier. So it turns out (in my case) the result of deqeueReusableCell function is not a MessageCell class even if i had registered exactly it
collectionView?.register(MessageCell.self, forCellWithReuseIdentifier: cellId)
moreover when i try to see is it real not the MessageCell instance by
cell.isKind(of: MessageCell.self)
it returns true. But at the same time when I am trying to access instance properties of MessageCell class it throws an error "Value of type 'UICollectionViewCell' has no member 'message"
But reference said:
After dequeueing the appropriate view in your delegate method,
configure its content and return it to the collection view for use.
and from reference of UICollectionViewDataSource:
Your implementation of this method is responsible for creating,
configuring, and returning the appropriate cell for the given item.
You do this by calling the
dequeueReusableCell(withReuseIdentifier:for:) method of the collection
view and passing the reuse identifier that corresponds to the cell
type you want.
Why do we need to upcast
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) ?
Here is MessageCell etc
class MessageCell: BaseCell {
var message: Message? {
didSet {
nameLabel.text = message?.friend?.name
if let profileImageName = message?.friend?.profileImageName {
profileImageView.image = UIImage(named: profileImageName)
messageLabel.text = message?.text
}
}
}
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 34
imageView.layer.masksToBounds = true
return imageView
}()
let dividerLineView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.5, alpha: 0.5)
return view
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Mark Zuckerberg"
label.font = UIFont.systemFont(ofSize: 18)
// label.backgroundColor = UIColor.brown
return label
}()
let messageLabel: UILabel = {
let label = UILabel()
label.text = "Your friends message and something else..."
label.textColor = UIColor.darkGray
label.font = UIFont.systemFont(ofSize: 14)
// label.backgroundColor = UIColor.green
return label
}()
let timelabel : UILabel = {
let label = UILabel()
label.text = "12:05 pm"
label.font = UIFont.systemFont(ofSize: 16)
label.textAlignment = .right
// label.textColor = UIColor.white
// label.backgroundColor = UIColor.black
return label
}()
let hasReadImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
return imageView
}()
override func setupViews() {
addSubview(profileImageView)
addSubview(dividerLineView)
profileImageView.image = UIImage(named: "zuckprofile")
hasReadImageView.image = UIImage(named: "zuckprofile")
setupContainerView()
addConstraintsWithFormat( "H:|-12-[v0(68)]|", views: profileImageView)
addConstraintsWithFormat( "V:[v0(68)]", views: profileImageView)
addConstraint(NSLayoutConstraint(item: profileImageView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
addConstraintsWithFormat( "H:|-82-[v0]|", views: dividerLineView)
addConstraintsWithFormat( "V:[v0(1)]|", views: dividerLineView)
}
func setupContainerView() {
let containerView = UIView()
addSubview(containerView)
addConstraintsWithFormat("H:|-90-[v0]|", views: containerView)
addConstraintsWithFormat( "V:[v0(50)]", views: containerView)
addConstraint(NSLayoutConstraint(item: containerView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
containerView.addSubview(nameLabel)
containerView.addSubview(messageLabel)
containerView.addSubview(timelabel)
containerView.addSubview(hasReadImageView)
containerView.addConstraintsWithFormat( "H:|[v0][v1(80)]-12-|", views: nameLabel, timelabel )
containerView.addConstraintsWithFormat( "V:|[v0(26)][v1(24)]|", views: nameLabel,messageLabel )
containerView.addConstraintsWithFormat( "H:|[v0]-8-[v1(20)]-12-|", views: messageLabel, hasReadImageView )
containerView.addConstraintsWithFormat("V:[v0(20)]|", views: hasReadImageView)
containerView.addConstraintsWithFormat( "V:|[v0(24)]", views: timelabel)
addConstraint(NSLayoutConstraint(item: nameLabel, attribute: .centerY, relatedBy: .equal, toItem: timelabel, attribute: .centerY, multiplier: 1, constant: -1.4 ))
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String , views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
class BaseCell : UICollectionViewCell {
let a = 5
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
layer.masksToBounds = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
backgroundColor = UIColor.blue
}
}
dequeueReusable is use for memory efficiency purpose. The way it works in lower level my friend has detail explanation on that https://medium.com/ios-seminar/why-we-use-dequeuereusablecellwithidentifier-ce7fd97cde8e
I hope it answered your question.

Custom View size in iOS not dynamic

I just have started my first app on iOS a week ago. I have created a custom view to use it in my app using AppleDeveloperWebsite Custom Rating Control Tutorial.
Now I have chosen iPhone7 device in storyboard and I run this on iPhone 7 emulator it works perfectly but when I run it on iPhone 5 emulator (size of screen changes) my custom views extend beyond the screen. My all other controls sizes resize as I set constraints but only my custom view sizes get messed up.
Please Help
import UIKit
#IBDesignable class xRadioButtonView: UIView {
var button: UIButton!
var label: UILabel!
#IBInspectable var text: String? {
didSet{
label.text = text
}
}
//Properties
var isSelected = 0
//Initialization
override init(frame: CGRect){
super.init(frame: frame)
addSubviews()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
addSubviews()
}
func addSubviews() {
self.backgroundColor = UIColor.white
let xWidth = bounds.size.width
let xHeight = bounds.size.height
let tap = UITapGestureRecognizer(target: self, action: #selector(xRadioButtonView.radioButtonTextTapped))
button = UIButton(frame: CGRect(x: 1, y: 1, width: xWidth - 2, height: xHeight - 4))
button.backgroundColor = UIColor.white
button.addTarget(self, action: #selector(xRadioButtonView.radioButtonTapped(button:)), for: .touchDown)
addSubview(button)
label = UILabel(frame: CGRect(x: 1, y: 1, width: xWidth - 2, height: xHeight - 2))
label.textColor = UIColor.init(hex: "#D5D5D5")
//label.font = UIFont.init(name: label.font.fontName, size: 25)
//label.font = label.font.withSize(25)
label.font = UIFont.boldSystemFont(ofSize: 25)
label.textAlignment = NSTextAlignment.center
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tap)
addSubview(label)
}
override func layoutSubviews() {
// Set the button's width and height to a square the size of the frame's height.
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
label.text = "xRBV"
}
func radioButtonTapped(button: UIButton) {
if isSelected == 0 {
isSelected = 1
self.backgroundColor = UIColor.init(hex: "#00BFA5")
label.textColor = UIColor.init(hex: "#00BFA5")
} else {
isSelected = 0
self.backgroundColor = UIColor.white
label.textColor = UIColor.init(hex: "#D5D5D5")
}
}
func radioButtonTextTapped(sender: UITapGestureRecognizer){
if isSelected == 0 {
isSelected = 1
self.backgroundColor = UIColor.init(hex: "#00BFA5")
label.textColor = UIColor.init(hex: "#00BFA5")
} else {
isSelected = 0
self.backgroundColor = UIColor.white
label.textColor = UIColor.init(hex: "#D5D5D5")
}
}
}
As you can see PG button should finish where green color finishes but white color button is extended beyond the screen
You either need to set the frame in layoutSubViews or you need to implement autolayout in code:
button = UIButton(frame: CGRect(x: 1, y: 1, width: xWidth - 2, height: xHeight - 4))
button.backgroundColor = UIColor.white
button.addTarget(self, action: #selector(xRadioButtonView.radioButtonTapped(button:)), for: .touchDown)
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
let attributes: [NSLayoutAttribute] = [.top, .bottom, .leading, .trailing]
let constants = [2, 2, 10, 10] // 2 from top and bottom, 10 from leading and trailing
NSLayoutConstraint.activate(attributes.enumerated().map { NSLayoutConstraint(item: button, attribute: $1, relatedBy: .equal, toItem: button.superview, attribute: $1, multiplier: 1, constant: constants[$0]) })
The example uses the old way because your constraints are uniform, but if you have something more complicated its often simpler to use NSLayoutAnchor as of iOS 9.
EDIT: here is the code for tuples if anyone is interested:
button.translatesAutoresizingMaskIntoConstraints = false
let attributes: [(NSLayoutAttribute, CGFloat)] = [(.top, 2), (.bottom, 2), (.leading, 12), (.trailing, 12)]
NSLayoutConstraint.activate(attributes.map { NSLayoutConstraint(item: button, attribute: $0.0, relatedBy: .equal, toItem: button.superview, attribute: $0.0, multiplier: 1, constant: $0.1) })
Thanks I wasn't even aware of this NSLayout yet. (HEHE 7 Days) Thanks to you I have a solution for my problem. Although I wanted different values for .top .bottom .leading .trailling
I used your code like this
NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: button.superview, attribute: .top, multiplier: 1, constant: 1).isActive = true
NSLayoutConstraint(item: button, attribute: .bottom, relatedBy: .equal, toItem: button.superview, attribute: .bottom, multiplier: 1, constant: 4).isActive = true
to all 4 sides. But is there a way to provide constant values as well like you have provided multiple attribute values?

UITableView damping animation and layout constraints

I'm trying to animate UITableView to act like a dropdownMenu by using its height constraint and UIView.animateWithDamping(..) block. I'm occuring weird problem with white background under tableView.
iPhone Simulator showing the problem
I have cleared each background color and it doesn't help much.
Here is the code setting all subviews of dropDownView, which is a UIView:
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.elements = []
defaultSetup()
}
private func defaultSetup() {
configureActionButton()
configureTableView()
}
private func configureActionButton() {
actionButton = UIButton(frame: CGRectZero)
actionButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(actionButton)
guard let superview = actionButton.superview else {
assert(false, "ActionButton adding to superview failed.")
return
}
// Constraints
actionButton.constrain(.Leading, .Equal, superview, .Leading, constant: 0, multiplier: 1)?.constrain(.Trailing, .Equal, superview, .Trailing, constant: 0, multiplier: 1)?.constrain(.Top, .Equal, superview, .Top, constant: 0, multiplier: 1)?.constrain(.Bottom, .Equal, superview, .Bottom, constant: 0, multiplier: 1)
// Appearance
actionButton.backgroundColor = UIColor.clearColor()
actionButton.opaque = false
actionButton.contentHorizontalAlignment = .Left
actionButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
if borderVisible {
actionButton.layer.cornerRadius = 5
actionButton.layer.borderColor = UIColor.blackColor().CGColor
actionButton.layer.borderWidth = 1
actionButton.clipsToBounds = true
}
// Actions
actionButton.addTarget(self, action: "menuAction:", forControlEvents: .TouchUpInside)
}
private func configureTableView() {
tableView = BOTableView(frame: CGRectZero, items: elements, configuration: configuration)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
addSubview(tableView)
guard let tableViewSuperview = tableView.superview else {
assert(false, "TableView adding to superview failed.")
return
}
// Constraints
tableView.constrain(.Trailing, .Equal, tableViewSuperview, .Trailing, constant: 0, multiplier: 1)?.constrain(.Top, .Equal, tableViewSuperview, .Bottom, constant: 0, multiplier: 1)?.constrain(.Leading, .Equal, tableViewSuperview, .Leading, constant: 0, multiplier: 1)
tvHeightConstraint = NSLayoutConstraint(item: tableView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0)
tableView.addConstraint(tvHeightConstraint)
}
BOTableView class initializer:
init(frame: CGRect, items: [String], configuration: BOConfiguration) {
super.init(frame: frame, style: UITableViewStyle.Plain)
self.items = items
self.selectedIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.configuration = configuration
// Setup table view
self.opaque = false
self.backgroundView?.backgroundColor = UIColor.clearColor()
self.backgroundColor = UIColor.clearColor()
self.separatorColor = UIColor.blackColor()
self.scrollEnabled = false
self.separatorStyle = .SingleLine
self.layer.cornerRadius = 5
self.layer.borderColor = UIColor.blackColor().CGColor
self.layer.borderWidth = 1
self.clipsToBounds = true
}
UIView animations:
private func showMenuWithCompletionBlock(completion: (succeeded: Bool) -> Void) {
delegate?.menuWillShow(self)
let tvHeight = frame.size.height * CGFloat(elements.count)
tvHeightConstraint.constant = tvHeight
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.5, options: .CurveEaseInOut, animations: { [weak self] () -> Void in
guard let strongSelf = self else {
completion(succeeded: false)
return
}
strongSelf.layoutIfNeeded()
}, completion: { (finished) -> Void in
if finished {
completion(succeeded: true)
}
})
}
Here is the code for UIView + Constraints extension, used in code:
extension UIView {
/**
:returns: true if v is in this view's super view chain
*/
public func isSuper(v : UIView) -> Bool
{
for var s : UIView? = self; s != nil; s = s?.superview {
if(v == s) {
return true;
}
}
return false
}
public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, _ otherView: UIView, _ otherAttribute: NSLayoutAttribute, constant: CGFloat = 0.0, multiplier : CGFloat = 1.0) -> UIView?
{
let c = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: otherView, attribute: otherAttribute, multiplier: multiplier, constant: constant)
if isSuper(otherView) {
otherView.addConstraint(c)
return self
}
else if(otherView.isSuper(self) || otherView == self)
{
self.addConstraint(c)
return self
}
assert(false)
return nil
}
public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, constant: CGFloat, multiplier : CGFloat = 1.0) -> UIView?
{
let c = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: nil, attribute: .NotAnAttribute, multiplier: multiplier, constant: constant)
self.addConstraint(c)
return self
}
}
When I tried to debug the views' hierarchy in debugger, the only view which had white background was tableView, but I have cleared the background in code. I have also tried to set tableView's backgroundView to nil as well as backgroundView.backgroundColor to clearColor(). Nothing changed.
Maybe try to set the UITableView footer to a blank view, don't really know why, but it seams to help for similar issue like You have.
[_tableView setTableFooterView:[[UIView alloc] init]];

Resources