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?
Related
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:
I have three buttons, when selected they change background color to red - yet a blue background is popping up as well and I can't figure out why (sorry if the reason is obvious, ive only been doing "programming" for 3 weeks. I can't find anything in the code that would explain itI have three buttons, when selected they change background color to red - yet a blue background is popping up as well and I can't figure out why (sorry if the reason is obvious, ive only been doing "programming" for 3 weeks. I can't find anything in the code that would explain it
[enter image description here][1]import UIKit
class ViewController: UIViewController {
var counterEN : Int = 0
var count = Array(1...150).filter { $0 % 1 != 0}.count
var pizzaCount = 0
#IBOutlet weak var pizzaCounter: UILabel!
#IBOutlet weak var textField: UITextField!
#IBAction func test(_ sender: UIButton) {
self.textField.text = sender.currentTitle
}
var lastY: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonClicked(_ sender: UIButton) {
pizzaCount = pizzaCount + 1
pizzaCounter.text = ("Antal pizza: ") + "\(pizzaCount)"
let contentView = UIView()
addViewsTo(contentView, title: sender.currentTitle)
contentView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(contentView)
// Add size constraints to the content view (260, 30)
NSLayoutConstraint(item: contentView, attribute: .width, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 850.0).isActive = true
NSLayoutConstraint(item: contentView, attribute: .height, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: ` 30.0).isActive = true
// Add position constraints to the content view (horizontal center, 100 from the top)
NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: lastY).isActive = true
NSLayoutConstraint(item: contentView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0).isActive = true
// Update last Y position to have the gaps between views to be 10
lastY += 50
}
// Add label and button to the content view
func addViewsTo(_ contentView: UIView, title: String?) {
// Add a label with size of (100, 30)
let label = UILabel()
label.text = title
//label.text = ("1 x 17 ")
label.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 30.0)
contentView.addSubview(label)
let DressingButton = UIButton(type: .system)
DressingButton.frame = CGRect(x: 210, y: 0, width: 40, height: 30)
DressingButton.setTitle("D", for: .normal)
DressingButton.setTitleColor(.black, for: .normal)
DressingButton.setTitleColor(.red, for: .selected)
DressingButton.sendActions(for: .touchUpInside)
contentView.addSubview(DressingButton)
DressingButton.addTarget(self, action: #selector(dressingAction(_:)), for .touchUpInside)
let Chili = UIButton(type: .system)
Chili.frame = CGRect(x: 160, y: 0, width: 40, height: 30)
Chili.setTitle("C", for: .normal)
Chili.setTitleColor(.black, for: .normal)
Chili.backgroundColor = UIColor.white
Chili.setTitleColor(.systemRed, for: .selected)
Chili.sendActions(for: .touchUpInside)
contentView.addSubview(Chili)
Chili.addTarget(self, action: #selector(dressingAction(_:)), for: .touchUpInside)
let Hvidløg = UIButton(type: .system)
Hvidløg.frame = CGRect(x: 110, y: 0, width: 40, height: 30)
Hvidløg.setTitle("H", for: .normal)
Hvidløg.setTitleColor(.black, for: .normal)
Hvidløg.setTitleColor(.red, for: .selected)
Hvidløg.backgroundColor = UIColor.white
Hvidløg.sendActions(for: .touchUpInside)
contentView.addSubview(Hvidløg)
Hvidløg.addTarget(self, action: #selector(dressingAction(_:)), for: .touchUpInside)
let button2 = UIButton(type: .system)
button2.frame = CGRect(x: -40, y: 20, width: 150, height: 30)
button2.setTitle("Alm", for: .normal)
button2.setTitle("Ful", for: .selected)
button2.setTitle("Glu", for: .selected)
// Set button action
button2.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
contentView.addSubview(button2)
self.view = view
}
#objc func dressingAction(_ sender:UIButton){
sender.isSelected.toggle()
print("knap")
sender.backgroundColor = UIColor.red
}
#objc func buttonAction(_ sender:UIButton!)
{
sender.isSelected = !sender.isSelected
sender.setTitle("Alm", for: .normal)
if sender.isSelected{
sender.setTitle("Fuld", for: .selected)}
else {
sender.setTitle("Glu", for: .selected)
sender.isSelected = !sender.isSelected
// sender.tintColor = .clear
//// if sender.isSelected{
// sender.setTitleColor(.green, for: .selected)
// }
// else{
// sender.setTitleColor(.blue, for: .selected)
//
func buttonAction(_ button3:UIButton!) {
print("Button tapped"); counterEN += 1; count += 1; print (counterEN); print (count)
}
}
}
}
Please change your button type to .custom and the issue will be solved.
let dressingButton = UIButton(type: .custom)
Try changing your button Type to custom
I've created a custom view in swift & am trying to get it to display appropriately. It is essentially a material card, with the ability to expand the view by pressing the more button. My issue comes in when specifying the bottom constraint. It is required, but setting it stretches my custom view.
I have implemented this already in android & I guess I'm trying to find the analog to android:height='wrap_content'. I've tried setting the aspect ratio constraint, which works to keep my view at the proper size, but prevents the custom view from expanding when its subviews change. Additionally I've tried using the lessThanOrEqualTo constraint, but that is too ambiguous to satisfy the bottom constraint.
This is what my UIExpandableCard view looks like:
import Foundation
import MaterialComponents
#IBDesignable
public class UIExpandableCard: UIView {
// attributes
#IBInspectable var overlineText: String? {
didSet {
overlineLabel.text = overlineText?.uppercased()
}
}
#IBInspectable var headlineText: String? {
didSet {
headlineLabel.text = headlineText
}
}
#IBInspectable var bodyText: String? {
didSet {
bodyLabel.text = bodyText
}
}
#IBInspectable var logoImage: UIImage? {
didSet {
logoImageView.image = logoImage
}
}
var cardView: MDCCard!
var overlineLabel: UILabel!
var headlineLabel: UILabel!
var bodyLabel: UILabel!
var moreButton: UIButton!
var logoImageView: UIImageView!
var isCardExpanded = false
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
self.translatesAutoresizingMaskIntoConstraints = false
setupViews()
setupConstraints()
}
private func setupViews() {
self.clipsToBounds = true
cardView = MDCCard(frame: CGRect.zero)
cardView.translatesAutoresizingMaskIntoConstraints = false
cardView.isInteractable = false
self.addSubview(cardView)
overlineLabel = UILabel(frame: CGRect.zero)
overlineLabel.translatesAutoresizingMaskIntoConstraints = false
overlineLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
overlineLabel.text = overlineText?.uppercased()
self.addSubview(overlineLabel)
headlineLabel = UILabel(frame: CGRect.zero)
headlineLabel.translatesAutoresizingMaskIntoConstraints = false
headlineLabel.font = UIFont.preferredFont(forTextStyle: .title1)
headlineLabel.text = headlineText
self.addSubview(headlineLabel)
bodyLabel = UILabel(frame: CGRect.zero)
bodyLabel.translatesAutoresizingMaskIntoConstraints = false
bodyLabel.font = UIFont.preferredFont(forTextStyle: .body)
bodyLabel.numberOfLines = 1
bodyLabel.text = bodyText
self.addSubview(bodyLabel)
logoImageView = UIImageView(image: logoImage)
logoImageView.translatesAutoresizingMaskIntoConstraints = false
logoImageView.contentMode = .scaleAspectFit
self.addSubview(logoImageView)
moreButton = UIButton(type: .roundedRect)
moreButton.isUserInteractionEnabled = true
moreButton.translatesAutoresizingMaskIntoConstraints = false
moreButton.setTitle("More", for: .normal)
moreButton.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
self.addSubview(moreButton)
}
#objc func buttonClicked(_ sender: UIButton) {
if !isCardExpanded {
moreButton.setTitle("Less", for: .normal)
bodyLabel.numberOfLines = 0
} else {
moreButton.setTitle("More", for: .normal)
bodyLabel.numberOfLines = 1
}
isCardExpanded = !isCardExpanded
}
private func setupConstraints() {
NSLayoutConstraint.activate([
cardView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8),
cardView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8),
cardView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
cardView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: -8),
overlineLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
overlineLabel.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16),
headlineLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
headlineLabel.topAnchor.constraint(equalTo: overlineLabel.bottomAnchor, constant: 8),
NSLayoutConstraint(item: logoImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 48),
NSLayoutConstraint(item: logoImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 48),
logoImageView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -16),
logoImageView.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16),
bodyLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
bodyLabel.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 16),
bodyLabel.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -8),
moreButton.topAnchor.constraint(greaterThanOrEqualTo: bodyLabel.bottomAnchor, constant: 8),
moreButton.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
moreButton.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -8)
])
}
}
Basically I want something like the left. However, I'm getting the right, where one of the views (in blue) is being stretched to fill the constraint.
left right
I'm relatively new to iOS, but have experience with android, so any explanations relating to that would be extra helpful.
Thanks.
So I found a solution that appears to do what I need. I'm still trying to wrap my head around what I was actually trying to achieve. Basically what I wanted was my view height to be defined by its subviews & their constraints, rather than having to specify it in a constraint. At the same time I needed to satisfy the height constraint in my interface.
My solution was as follows: add a low priority height constraint of 0 to the the card view in my interface. This satisfies the requirement for a height in the scene, while also allowing my view to expand & contract without being stretched.
... a low priority constraint of height zero for the view as a whole. The low priority constraint will try to shrink the assembly, while the other constraints stop it from shrinking so far that it clips its subviews.
I found this solution on another stack overflow question.
Following Joshua's answer, with this I prevented a custom view from stretching:
let width = widthAnchor.constraint(equalToConstant: 0)
width.priority = .defaultLow
width.isActive = true
You can't do that with lessThanOrEqualTo.Although i can't fully understand you're question but hope this approach help.
first of all define a flow constraint like:
var heightConstraint: NSLayoutConstraint?
and replace it with bottom constraint in setupConstraint:
private func setupConstraints() {
heightConstraint = cardView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([
cardView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8),
cardView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8),
cardView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
heightConstraint,
overlineLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
overlineLabel.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16),
.
.
.
and finally when press the button:
private func expandViewWithAnimation(_ isExpand: Bool) {
UIView.animate(withDuration: 0.8) {
self.heightConstraint?.constant = isExpand ? 80:0
self.layoutIfNeeded()
}
}
#objc func buttonClicked(_ sender: UIButton) {
if !isCardExpanded {
moreButton.setTitle("Less", for: .normal)
bodyLabel.numberOfLines = 0
} else {
moreButton.setTitle("More", for: .normal)
bodyLabel.numberOfLines = 1
}
expandViewWithAnimation(isCardExpanded)
isCardExpanded = !isCardExpanded
}
i can't comment on your post because don't have enough reputation :)
Actually i have a project in github. The problem is that i cannot get why when scrolling, the buttons constraints in the cells are going crazy..
I didn't saw any project like this, then i have a reason to share it but i want to give a good example for another people.
I'll be very thankful with any help that drives me to the solution of this problem.
Best regards.
There it is the code for the cell:
import UIKit
class BookTableViewCell: UITableViewCell {
let nameLabel = UILabel()
let detailLabel = UILabel()
var cellButton = UIButton()
var cellLabel = UILabel()
var book : Book!
// MARK: Initalizers
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
func configureCellCon(botones:Int, titulo:String, book:Book) {
self.book = book
let marginGuide = contentView.layoutMarginsGuide
// configure titleLabel
contentView.addSubview(nameLabel)
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
nameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
nameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
nameLabel.numberOfLines = 0
nameLabel.font = UIFont(name: "AvenirNext-DemiBold", size: 16)
nameLabel.text = book.name
// configure authorLabel
contentView.addSubview(detailLabel)
detailLabel.translatesAutoresizingMaskIntoConstraints = false
detailLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
// detailLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
detailLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
detailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor).isActive = true
detailLabel.numberOfLines = 0
detailLabel.font = UIFont(name: "Avenir-Book", size: 12)
detailLabel.textColor = UIColor.lightGray
detailLabel.text = book.details
var lastButton = UIButton()
if(book.buttonsAttibutes.count == 1) {
let button = UIButton()
button.tag = 0
button.backgroundColor = UIColor.init(white: 0.9, alpha: 0.0)
// button.setBackgroundColor(color: UIColor.white, forState: .normal)
// button.setBackgroundColor(color: UIColor.blue, forState: .normal)
button.setTitle(book.buttonsAttibutes[0].title, for: .normal)
button.setTitleColor(UIColor.blue.withAlphaComponent(0.7), for: .normal)
button.setTitleColor(UIColor.init(white: 0.8, alpha: 1), for: .highlighted)
button.layer.borderWidth = 0
button.layer.borderColor = UIColor.blue.cgColor
contentView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraints([
NSLayoutConstraint(item: button, attribute: .leftMargin, relatedBy: .equal, toItem: contentView, attribute: .leftMargin, multiplier: 1.0, constant: 20),
NSLayoutConstraint(item: button, attribute: .rightMargin, relatedBy: .equal, toItem: contentView, attribute: .rightMargin, multiplier: 1.0, constant: -20),
])
button.topAnchor.constraint(equalTo: detailLabel.bottomAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
button.addTarget(self, action:#selector(mostrarMensaje), for:.touchUpInside)
return
}else if(book.buttonsAttibutes.count == 0) {
detailLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
return
}
if(book.buttonsAttibutes.count > 0) {
for x in 0...(book.buttonsAttibutes.count - 1) {
let button = UIButton()
button.tag = x
button.backgroundColor = UIColor.init(white: 0.9, alpha: 0.0)
// button.setBackgroundColor(color: UIColor.white, forState: .normal)
// button.setBackgroundColor(color: UIColor.blue, forState: .normal)
button.setTitle(book.buttonsAttibutes[x].title, for: .normal)
button.setTitleColor(UIColor.blue.withAlphaComponent(0.7), for: .normal)
button.setTitleColor(UIColor.init(white: 0.8, alpha: 1), for: .highlighted)
button.layer.borderWidth = 0
button.layer.borderColor = UIColor.blue.cgColor
contentView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
// button.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
// button.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
contentView.addConstraints([
NSLayoutConstraint(item: button, attribute: .leftMargin, relatedBy: .equal, toItem: contentView, attribute: .leftMargin, multiplier: 1.0, constant: 20),
NSLayoutConstraint(item: button, attribute: .rightMargin, relatedBy: .equal, toItem: contentView, attribute: .rightMargin, multiplier: 1.0, constant: -20),
])
if(x == 0){
if #available(iOS 11.0, *) {
button.layer.cornerRadius = 8
button.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else {
button.layer.cornerRadius = 5
}
button.topAnchor.constraint(equalTo: detailLabel.bottomAnchor).isActive = true
}else if(x == (book.buttonsAttibutes.count - 1)){
if #available(iOS 11.0, *) {
button.layer.cornerRadius = 8
button.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
} else {
button.layer.cornerRadius = 5
}
button.topAnchor.constraint(equalTo: lastButton.bottomAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
}else{
button.topAnchor.constraint(equalTo: lastButton.bottomAnchor).isActive = true
}
button.addTarget(self, action:#selector(mostrarMensaje), for:.touchUpInside)
lastButton = button
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func mostrarMensaje(sender: UIButton){
let message = self.book.buttonsAttibutes[sender.tag].message
let alertController = UIAlertController(title: "\(self.book.name)", message: message, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Cerrar", style: UIAlertActionStyle.default) {
(result : UIAlertAction) -> Void in
}
alertController.addAction(okAction)
if let myViewController = parentViewController {
print(myViewController.title ?? "ViewController without title.")
myViewController.present(alertController, animated: true, completion: nil)
}
}
}
Ok, i went for the "InterfaceBuilder" and did it properly.
However, I'll be reading more about programmatic methods to do it.
thanks to all of you for the guide.
When creating the buttons and constraints programmatically, they are generating more constraints over the same cells making them crash.
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: