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:
Related
Working on specing out a view in Playground and can't seem to figure out why UIIMageView is being placed in the center of a UICollectionViewCell.
Relevant Code:
class BookCell: UICollectionViewCell {
static let identifier = "bookCell"
override init(frame: CGRect) {
super.init(frame: .zero)
self.layer.cornerRadius = 12
self.backgroundColor = .brown
addAllSubviews()
addAllConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var cover: UIImageView = {
let imageview = UIImageView()
imageview.translatesAutoresizingMaskIntoConstraints = false
var largeImage = UIImage(named: "medium.jpg")
imageview.image = largeImage
imageview.contentMode = .scaleAspectFit
//imageview.contentMode = .scaleToFill
return imageview
}()
func coverConstraints(){
NSLayoutConstraint.activate([
cover.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0),
/**widthConstraint*/
NSLayoutConstraint(item: cover,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 1.0, constant: 0.0),
/**heightConstraint*/
NSLayoutConstraint(item: cover,
attribute: .height,
relatedBy: .equal,
toItem: self,
attribute: .height,
multiplier: 0.75, constant: 0.0)
])
}
let wordLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "test"
return label
}()
func wordLabelConstraints() {
NSLayoutConstraint.activate([
wordLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
wordLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
wordLabel.topAnchor.constraint(equalTo: cover.bottomAnchor, constant: 2)
])
}
// MARK: - Add Subviews
func addAllSubviews() {
self.addSubview(cover)
self.addSubview(wordLabel)
}
// MARK: - SubViews Constraints
func addAllConstraints() {
coverConstraints()
wordLabelConstraints()
}
}
BookCell is then used in a UICollectionViewController like so:
class ViewController: UIViewController {
fileprivate let collectionView: UICollectionView = {
let layout = ColumnFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .blue
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
var data: [Int] = Array(0..<10)
override func loadView() {
super.loadView()
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16)
])
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(BookCell.self, forCellWithReuseIdentifier: BookCell.identifier)
self.collectionView.alwaysBounceVertical = true
self.collectionView.backgroundColor = .yellow
}
}
Result:
I noticed that using scaleToFill instead of scaleAspectFit results in image covering the entire width of the cell. The result (see image below) fits what I am aiming for but ... see question below
Question:
Is using scaleToFill the only way to pin an image to the edges (leading and trailing) of UICollectionViewCell. If so, why is this?
I also tried adding the UIImageView to a UIStackView and I believe I got the same results.
Please note that I am not interested in doing this via Storyboard.
Thank you for providing feedback
There is one more option: .scaleAspectFill but may be cropped your image's content.
The option to scale the content to fill the size of the view.
Some portion of the content may be clipped to fill the view’s bounds.
Think about getting the image size ratio (width/height), you having a fixed width based on superview, and height will be based on the image Ratio.
EDITED:
This is my custom cell class. It has a TextField and a TextView. Whatever I do I can't get the row height updated automatically. I know I can do it manually using heightForRowAt but I don't want to do that.
class customCell: UITableViewCell, UITextViewDelegate{
var didSetupConstraints = false
var titleField : UITextField = {
var textField = UITextField()
textField.placeholder = " Subject (optional)"
textField.backgroundColor = UIColor.lightGray
textField.translatesAutoresizingMaskIntoConstraints = false
textField.layer.cornerRadius = 3
textField.clipsToBounds = true
return textField
}()
var messageView : UITextView = {
var textView = UITextView()
textView.text = "Add your email here"
textView.translatesAutoresizingMaskIntoConstraints = false
textView.backgroundColor = UIColor.red
return UITextView()
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(titleField)
self.contentView.addSubview(messageView)
messageView.delegate = self
addConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addConstraints(){
contentView.addConstraints([titleField.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 23),titleField.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -18),titleField.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 18) ])
titleField.addConstraint(NSLayoutConstraint(item: titleField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50))
contentView.addConstraints([messageView.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: 11),messageView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -18),messageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 18), messageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5)])
messageView.addConstraint(NSLayoutConstraint(item: messageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100))
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.setNeedsLayout()
contentView.layoutIfNeeded()
}
override func updateConstraints() {
if !didSetupConstraints {
addConstraints()
didSetupConstraints = true
}
super.updateConstraints()
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Add your email here"
textView.textColor = UIColor.lightGray
}
}
}
I have already seen this question and from what I have understood the things I need to do are:
Add tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension which I have done in tableViewController
Add a bottom and top constraint: I have added a topAnchor to my TextField + a constraint between my TextField and TextView + a constraint between my TextView's bottomAnchor and the contentView bottomAnchor
I have added my constraints code into my updateConstraints() method.
Not sure if I need to do anything else, but I've done all three but it still doesn't work. I'm guessing that maybe my bottom/top constraints are not set up correctly. The current result that I get is (The textView isn't visible at all :(( )
yet what I expect to get is:
EDIT 2
See image:
After all the fixes, the only problem I have now is that the empty cells don't have the default size of 44, is it that the tableView is trying to be smart and adjusts the row height based on the last cell height?
A few things:
updateConstraints can be called multiple times by the system, so use a flag to only add your constraints the first time.
messageView.topAnchor.constraint(equalTo: titleField.topAnchor, constant: 11) should be messageView.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: 11)
Try giving your messageView a height.
As #Honey pointed out, textView was not returned in the initialization of messageView.
About empty cell heights, if you don't want empty cells at all, just do tableView.tableFooterView = UIView() to get rid of them. It's probably the table view being smart about cell heights, like you said.
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:
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?
I am trying to achieve this in swift.
So far I created my own custom view which is a subclass of UIView class:
class MyConnections: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 1)
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
let circle = CGRectMake(5, 60, 80, 80)
CGContextAddEllipseInRect(context, circle)
CGContextStrokePath(context)
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextFillEllipseInRect(context, circle)
}
}
This is my view controller where I add the above view as a subview:
let profile = MyConnections()
override func viewDidLoad() {
super.viewDidLoad()
profile.backgroundColor = UIColor.clearColor()
view.addSubview(profile)
self.profile.setTranslatesAutoresizingMaskIntoConstraints(false)
//constraints for the location button
let horizontalConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 10)
let verticalConstraint = NSLayoutConstraint(item: self.profile
, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20)
let widthConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 150)
let heightConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 150)
self.view.addConstraints([verticalConstraint, horizontalConstraint, widthConstraint, heightConstraint])
// Do any additional setup after loading the view.
}
All the code above gives me a circle on top. Now I want to repeat that same circle multiple times at different positions as seen in the image. I can create multiple instances of the uiview add them as subview but every time I will have to define new constraints for it which I don't want to do.
Can anyone please help me and give me an efficient answer?
You should know a UIView can have a single superview/parent. If you add it as a subview at a different position (using addSubview method) it will be removed from the first position and added as a subview to the new position.
In your case to add more subviews you have to create more UIView objects not use a single global UIView.
If the layout is repetitive a UITableView / UICollectionView is a better choice.
Your requirement and UI qualify for a UICollectionView I think you should use UICollectionView and can create a custom UICollectionViewCell with round image and and badge view as well, and they add dataSource and delegate methods. That will not only help in creating UI but it will make your app more performant by reusing cells
Here is a nice tutorial about UICollectionView
Here's a simplified view to create collection view programmatically:
make the collection view and layout programmatically just like any other view you would code and add it as subview like below:
lazy var myCollectionView : UICollectionView = {
let layout = YourFlowLayout()
layout.scrollDirection = self.direction;
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
cv.isPagingEnabled = true
cv.backgroundColor = UIColor.clear
cv.showsHorizontalScrollIndicator = false
cv.showsVerticalScrollIndicator = false
cv.allowsMultipleSelection = false
return cv
}()
and your flow layout could be something like:
mport UIKit
class Yourflowlayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return super.layoutAttributesForElements(in: rect)?.map {
attrs in
let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
self.applyLayoutAttributes(attributes: attrscp)
return attrscp
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if let attrs = super.layoutAttributesForItem(at: indexPath as IndexPath) {
let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
self.applyLayoutAttributes(attributes: attrscp)
return attrscp
}
return nil
}
func applyLayoutAttributes(attributes : UICollectionViewLayoutAttributes) {
if attributes.representedElementKind != nil {
return
}
if let collectionView = self.collectionView {
let stride = (self.scrollDirection == .horizontal) ? collectionView.frame.size.width : collectionView.frame.size.height
let offset = CGFloat(attributes.indexPath.section) * stride
var xCellOffset : CGFloat = CGFloat(attributes.indexPath.item) * self.itemSize.width
var yCellOffset : CGFloat = CGFloat(attributes.indexPath.item) * self.itemSize.height
if(self.scrollDirection == .horizontal) {
xCellOffset += offset;
} else {
yCellOffset += offset
}
attributes.frame = CGRect(x: xCellOffset, y: yCellOffset, width: self.itemSize.width, height: self.itemSize.height)
}
}
}
You can add the collectionView in your other classes as a subview , make sure you have the
myCollectionView.translatesAutoresizingMaskIntoConstraints = false so that your constrains are applied and you actually see the collection view and of course add your constrains or give it a frame.
Hope that helps someone.