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.
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 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
}
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:
Please i need help with the below, i just start learning how to design interface programmatically, after few tutorials i wanted to try something, then i got stucked
I am trying to achieve the below image
but this is what i got
this is my code below
class FeedsCell: UICollectionViewCell{
override init(frame: CGRect){
super.init(frame: frame)
setupViews()
}
let thumNailImageView : UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = UIColor.blue
return imageView
}()
let sourceName:UILabel = {
let srcLabel = UILabel()
srcLabel.backgroundColor = UIColor.purple
srcLabel.translatesAutoresizingMaskIntoConstraints = false
return srcLabel
}()
let separatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
return view
}()
func setupViews(){
addSubview(thumNailImageView)
addSubview(separatorView)
addSubview(sourceName)
addConstraintsWithFormat(format: "H:|-16-[v0(194)]", views: thumNailImageView)
addConstraintsWithFormat(format: "V:|-16-[v0]-16-[v1(1)]|", views: thumNailImageView, separatorView)
addConstraintsWithFormat(format: "H:|[v0]|", views: separatorView)
//left Constriants
addConstraint(NSLayoutConstraint(item: sourceName, attribute: .left, relatedBy: .equal, toItem: thumNailImageView, attribute: .right, multiplier: 1, constant: 8))
//Right constraints
addConstraint(NSLayoutConstraint(item: sourceName, attribute: .right, relatedBy: .equal, toItem: thumNailImageView, attribute: .right, multiplier: 1, constant: 0))
addConstraintsWithFormat(format: "H:|-8-[v0]-8-|", views: sourceName)
addConstraintsWithFormat(format: "V:|-8-[v0(20)]", views: sourceName)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIView{
func addConstraintsWithFormat(format: String, views: UIView...){
var viewDictionary = [String: UIView]()
for(index, view) in views.enumerated(){
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewDictionary))
}
}
I have been able to solve the issue using SnapKit, to achieve the image did something like this
let screenFrame = UIScreen.main.bounds
thumNailImageView.snp.makeConstraints { (make) in
make.width.equalTo(194)
make.top.equalTo(contentView).offset(20)
make.left.equalTo(contentView).offset(20)
make.bottom.equalTo(contentView).offset(-20)
}
sourceName.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(20)
make.top.equalTo(contentView).offset(20)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}
postTitle.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(30)
make.top.equalTo(sourceName).offset(30)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}
timeStamp.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(10)
make.top.equalTo(postTitle).offset(40)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}
postContent.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(60)
make.top.equalTo(timeStamp).offset(20)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}
I'm trying to make a UIScrollView containing a UIStackView with several layers of stack views nested inside. I would like to use AutoLayout, but there is something wrong with what I'm doing, and my brain is getting scrambled trying to figure it out:
import UIKit
class HomeView: UIView {
let looks = sampleLooks
let user = sampleUser
let streamView: UIStackView = UIStackView(arrangedSubviews: [])
let scrollView: UIScrollView = UIScrollView()
func makeButtonWithTitle(title: String, image: UIImage?, selector: String, tag: Int) -> UIButton {
let button = UIButton(type: .System)
button.setImage(image, forState: .Normal)
button.tintColor = UIColor.blackColor()
switch tag {
case 0...10:
button.backgroundColor = UIColor(white: 0.98, alpha: 0.8)
button.titleLabel?.font = UIFont(name: "HelveticaNeue-Thin", size: 30)
default:
button.backgroundColor = UIColor(white: 0.90, alpha: 1.0)
button.titleLabel?.font = UIFont(name: "HelveticaNeue-Thin", size: 40)
}
button.setTitle(title, forState: .Normal)
button.tag = tag
return button
}
func makeMessageView(senderImage: UIImage, senderHandle: String, text: String) -> UIView {
let textView = UILabel()
textView.text = text
textView.font = UIFont(name: "HelveticaNeue-Thin", size: 20)
let senderLabel = UILabel()
senderLabel.text = senderHandle
textView.font = UIFont(name: "HelveticaNeue-Thin", size: 20)
let textStackView = UIStackView(arrangedSubviews:[senderLabel, textView])
textStackView.axis = .Horizontal
textStackView.alignment = .Fill
textStackView.distribution = .Fill
let postView = UIView()
postView.addSubview(textStackView)
postView.backgroundColor = UIColor(white: 0.98, alpha: 0.8)
return postView
}
required init?(coder:NSCoder) {
super.init(coder:coder)
self.contentMode = .ScaleToFill
self.backgroundColor = UIColor(patternImage: UIImage(named: "background")!)
self.streamView.spacing = 20.0
self.streamView.translatesAutoresizingMaskIntoConstraints = false
self.streamView.axis = .Vertical
self.streamView.alignment = .Fill
self.streamView.distribution = .FillEqually
for look in self.looks {
let lookImageView = UIImageView(image: look.photo.photo)
lookImageView.contentMode = .ScaleAspectFit
lookImageView.clipsToBounds = true
let postView = self.makeMessageView(
look.user.photo.photo, senderHandle: look.user.handle, text: look.post)
let likeButton = self.makeButtonWithTitle(
" Like", image: UIImage(named: "like"), selector: "", tag: 0)
let commentButton = self.makeButtonWithTitle(
" Comment", image: UIImage(named: "SMS"), selector: "", tag: 1)
let shareButton = self.makeButtonWithTitle(
" Share", image: UIImage(named: "upload"), selector: "", tag: 2)
let buttonsView = UIStackView(arrangedSubviews: [likeButton, commentButton, shareButton])
buttonsView.distribution = .FillEqually
let lookView = UIStackView(arrangedSubviews:[lookImageView, postView, buttonsView])
lookView.axis = .Vertical
lookView.distribution = .Fill
lookView.alignment = .Fill
self.streamView.addArrangedSubview(lookView)
}
self.scrollView.addSubview(self.streamView)
self.scrollView.frame = UIScreen.mainScreen().bounds
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false
self.addSubview(self.scrollView)
}
}
So, what I would like this code to do is to give me a scrollable stack of nested stacks that covers the width of the screen (it's ok if the images are clipped, but I'd like them to cover the screen without distortion). What it actually gives me is a scrollable set of images where the width of the first image (seemingly) determines the width of the stack. I'm actually not sure if that's what's going on, but the top level UIStackView is not covering the width of the screen.
I think it has something to do with the UIScrollView has no intrinsic width, so the stack view inside it decides its own size. I say this because if I put the stack view directly in the parent view, it covers the display, but then as you might expect, there is no scrolling...
You need to set some constraints between the scrollview and the UIStackView it contains, at the moment you don't have any.
These three are enough to make the inner UIStackView the same size of the scrollview:
Horizontal constraint between the scrollview and the UIStackView, no space between them
Vertical constraint between the scrollview and the UIStackView, no space between them, this way you'll be able to scroll for the full height of the UIStackView
Same width for the scrollview and the UIStackView, this way the UIStackView will match the scrollview width
And this is the code:
//You already have these two lines:
//scrollView.addSubview(streamView)
//streamView.translatesAutoresizingMaskIntoConstraints = false;
//Add this one too:
scrollView.translatesAutoresizingMaskIntoConstraints = false;
scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("V:|[innerView]|",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil,
views: ["innerView":streamView]))
scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("H:|[innerView]|",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil,
views: ["innerView":streamView]))
scrollView.addConstraint(
NSLayoutConstraint(item: scrollView,
attribute: .Width,
relatedBy: .Equal,
toItem: streamView,
attribute: .Width,
multiplier: 1.0,
constant: 0))
Alternatively, you can extend UIView with a method that embed your view in a scrollview:
extension UIView {
func embedInScrollView()->UIView{
let cont=UIScrollView()
self.translatesAutoresizingMaskIntoConstraints = false;
cont.translatesAutoresizingMaskIntoConstraints = false;
cont.addSubview(self)
cont.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[innerView]|", options: NSLayoutFormatOptions(rawValue:0),metrics: nil, views: ["innerView":self]))
cont.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[innerView]|", options: NSLayoutFormatOptions(rawValue:0),metrics: nil, views: ["innerView":self]))
cont.addConstraint(NSLayoutConstraint(item: self, attribute: .Width, relatedBy: .Equal, toItem: cont, attribute: .Width, multiplier: 1.0, constant: 0))
return cont
}
}
And use it this way:
let scrollView = streamView.embedInScrollView()
Edit: Fixed last constraint in the first snippet.