Resizing a multi line UILabel within a UIScrollView breaks scrolling. Why? - ios

I use the following setup:
The ViewControllers view holds a UIScrollView with Top, Leading, Trailing and Bottom constraints to match the VCs size
The ScrollView contains two subviews:
A UIView to define the content size of the scroll view. It has the same height as the ScrollView but twice its width. Thus only horizontal scrolling is possible.
A UILabel with some long text with a Height and Width constraint to set a fixed size and a Top and Leading constraint to the ScrollView to set a fixed position.
The width of the Label is changed when the ScrollView scrolls.
Problem: If the Label is set to use more than one line AND the ScrollViews contenOffset property is set manually the ScrollView stops scrolling.
ViewController View
+---------------------+
|+-------------------+|
||ScrollView ||
||+------------------||--------------------+
|||UIView to define || content size |
||| || |
||| || |
||| [MultiLine] || |
||| [ Label ] || |
||| || |
||+------------------||--------------------+
|+-------------------+|
+---------------------+
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// Setting the ContentOffset will stop scrolling
//[self.scrollView setContentOffset:CGPointMake(0, 0)];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// Resize Label when scrolling
self.labelWidthConstraint.constant = MAX (50, 50 + self.scrollView.contentOffset.x);
}
Resizing the label using this code works fine if
the label is set use one line. In this case setting the setting the content offset does NOT do any harm. OR
the content offset is not changes (not even set to (0, 0)). In this case setting the label to multi line does NOT do any harm
Setting the content offset AND using multi line at the same time DOES NOT work. The scroll cannot be scrolled any more.
Why is this? Any idea what might cause this and how to solve it?

The issue appears to be that when the label constraints are changed it triggers viewDidLayoutSubviews which then sets the UIScrollView to not scroll since set contentOffset is then called over and over. You could overcome this if you are only wanting to set the UIScrollView to CGPoint.zero on the initial layout by using a bool as a flag. Apparently since UILabel needs a redraw on size changes it triggers viewDidLayoutSubviews. Here is an example in Swift.
import UIKit
class ViewController: UIViewController {
lazy var scrollView : UIScrollView = {
let sv = UIScrollView(frame: self.view.bounds)
sv.translatesAutoresizingMaskIntoConstraints = false
sv.delegate = self
return sv
}()
lazy var contentView : UIView = {
let v = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width * 4, height: self.view.bounds.height))
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
lazy var label : UILabel = {
let lbl = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 50))
lbl.numberOfLines = 0
lbl.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"
lbl.minimumScaleFactor = 0.5
lbl.adjustsFontSizeToFitWidth = true
lbl.font = UIFont.systemFont(ofSize: 22)
lbl.translatesAutoresizingMaskIntoConstraints = false
return lbl
}()
var widthConstraint : NSLayoutConstraint?
var heightConstraint : NSLayoutConstraint?
var startingHeight : CGFloat = 0
var startingWidth : CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
//first scrollview
self.view.addSubview(scrollView)
pinToAllSides(target: scrollView)
//now content view
self.scrollView.addSubview(contentView)
contentView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 2).isActive = true
contentView.heightAnchor.constraint(equalTo: self.scrollView.heightAnchor, multiplier: 1).isActive = true
contentView.backgroundColor = .green
pinToAllSides(target: contentView)
scrollView.layoutIfNeeded()
//now the label
self.scrollView.addSubview(label)
label.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor, constant: 20).isActive = true
label.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: 60).isActive = true
label.backgroundColor = .red
widthConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.view.bounds.width/2)
heightConstraint = NSLayoutConstraint(item: label, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 300)
if let wc = widthConstraint,
let hc = heightConstraint{
startingHeight = hc.constant
startingWidth = wc.constant
label.addConstraint(wc)
label.addConstraint(hc)
}
}
func pinToAllSides(target:UIView){
guard let superview = target.superview else{
return
}
target.leadingAnchor.constraint(equalTo: superview.leadingAnchor).isActive = true
target.trailingAnchor.constraint(equalTo: superview.trailingAnchor).isActive = true
target.topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
target.bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
}
var hasHappenedOnce : Bool = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if hasHappenedOnce == false{
hasHappenedOnce = true
self.scrollView.contentOffset = .zero
}
}
}
extension ViewController : UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//hopefully it is laggy due to simulator but for the label i would ditch constraints myself
self.widthConstraint?.constant = max(startingWidth, self.scrollView.contentOffset.x * 1.1 + startingWidth)
let height = startingHeight - self.scrollView.contentOffset.x
self.heightConstraint?.constant = height
label.updateConstraints()
}
}

The problem is here:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// Setting the ContentOffset will stop scrolling
[self.scrollView setContentOffset:CGPointMake(0, 0)];
}
Changing self.labelWidthConstraint.constant when scrolling the scroll view triggers viewDidLayoutSubviews! So, as soon as you start scrolling, your code immediately resets .contentOffset to 0,0.
I don't know why you would want to be calling setContentOffset anyway, certainly not in viewDidLayoutSubviews.
Doing a quick test, after removing the viewDidLayoutSubviews code...
I called [self.scrollView setContentOffset:CGPointMake(0, 0)]; at the end of viewDidload (and also tried in viewDidAppear)... the scrolling (and constraint constant updating) continues to work fine.
I also added a button that calls [self.scrollView setContentOffset:CGPointMake(0, 0)]; when tapped... the scrolling (and constraint constant updating) continues to work fine.

Related

(Swift 5) UIScrollView scrolls but none of the content scrolls (video included)

I'm trying to learn to build views without storyboard. I tried to build a scrollview. On that scrollview is a UISearchBar, a UIImageView with an image and a UILabel. It works but none of the content moves. The content is all just frozen in place like no matter how far I scroll the search bar will always be on top of the page. and the image on the bottom. I've attached a video to show what I mean. There's also a problem because none of the content is where I want it to be but that's another problem. I realize this is probably because I don't know enough about constraints and autolayout and building views without storyboards.
Here's the video
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label = UILabel()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
// setupLayout()
// tried this here doesn't do anything for me
}
func setupLayout() {
view.addSubview(scrollView)
self.scrollView.addSubview(searchBar)
homeImage.image = UIImage(named: "Treehouse")
self.scrollView.addSubview(homeImage)
label.text = "Inspiration for your next trip..."
self.scrollView.addSubview(label)
// not sure where this label is being added I want it to be underneath the image but it isn't t
let safeG = view.safeAreaLayoutGuide
let viewFrame = view.bounds
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: -10),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
searchBar.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 50.0),
searchBar.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.9),
searchBar.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 150),
homeImage.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 1.1),
homeImage.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100)
])
// was doing all this in viewDidLayoutSubviews but not sure if this is better place for it
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupLayout()
// tried this in viewDidLoad() and it didn't solve it.
}
}
any help would be appreciated
First, when constraining subviews in a UIScrollView, you should constrain them to the scroll view's Content Layout Guide. You're constraining them to the view's safe area layout guide, so they're never going to go anywhere.
Second, it's difficult to center subviews in a scroll view, because the scroll view can scroll both horizontally and vertically. So it doesn't really have a "center."
You can either put subviews in a stack view, or, quite common, use a UIView as a "content" view to hold the subviews. If you constrain that content view's Width to the scroll view's Frame Layout Guide width, you can then horizontally center the subviews.
Third, it can be very helpful to comment your constraints, so you know exactly what you expect them to do.
Here's a modified version of your posted code:
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
// don't do this
//scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
setupLayout()
}
func setupLayout() {
view.addSubview(scrollView)
//homeImage.image = UIImage(named: "Treehouse")
homeImage.image = UIImage(named: "natureBKG")
label.text = "Inspiration for your next trip..."
// let's use a UIView to hold the "scroll content"
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
// give it a green background so we can see it
contentView.backgroundColor = .green
contentView.addSubview(searchBar)
contentView.addSubview(homeImage)
contentView.addSubview(label)
scrollView.addSubview(contentView)
let safeG = view.safeAreaLayoutGuide
let svContentG = scrollView.contentLayoutGuide
let svFrameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView to all 4 sides of view
// (generally, constrain to safe-area, but this is what you had)
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// constrain contentView to all 4 sides of scroll view's Content Layout Guide
contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: 0.0),
// constrain contentView Width equal to scroll view's Frame Layout Guide Width
contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor),
// constrain searchBar Top to contentView Top + 50
searchBar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 50.0),
// constrain searchBar Width to 90% of contentView Width
searchBar.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.9),
// constrain searchBar centerX to contentView centerX
searchBar.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Top to searchBar Bottom + 40
homeImage.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 40.0),
// constrain homeImage Width equal to contentView Width
homeImage.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0),
// constrain homeImage centerX to contentView centerX
homeImage.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Height to 1/2 of scroll view frame Height
homeImage.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.5),
// you probably won't get vertical scrolling yet, so increase the vertical space
// between the homeImage and the label by changing the constant
// from 100 to maybe 400
// constrain label Top to homeImage Bottom + 100
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100.0),
// constrain label centerX to contentView centerX
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain label Bottom to contentView Bottom - 20
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0),
])
}
}

Center UIImageView within a UIScrollView with a larger contentSize

I have a full screen scrollView, to which I add an imageView as subview. I want the imageView to be centered and scaled filling the scrollView's size (that is the screen size) at the beginning, but then to allow the user to scroll the image in both directions (vertical and horizontal) with equal offsets at left, right, top and bottom.
I mean: I've set the scroll view's contentSize to be CGSize(width: screenWidth + 200, height: screenHeight + 200), and if I run the app, I see that I am able to scroll those 200 pts of offset only to the right and to the bottom of the image. I'd like the image to be centered in the content size, and to be able to scroll it horizontally to both to the left and to the right with offset 100 pts each side (similar thing with top and bottom when scrolling vertically).
How could I achieve this?
Note: I'm setting all the UI in code, I'm not using storyboards nor xib files
You may find it easier / more intuitive to use constraints and auto-layout rather than screenWidth and screenHeight:
//
// CenteredScrollViewController.swift
// SW4Temp
//
// Created by Don Mag on 4/18/18.
//
import UIKit
class CenteredScrollViewController: UIViewController {
let theScrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.green
return v
}()
let theImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.blue
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scrollView to the main view
view.addSubview(theScrollView)
// add the imageView to the scrollView
theScrollView.addSubview(theImageView)
// pin the scrollView to all four sides
theScrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
theScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0).isActive = true
theScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
theScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true
// constrain the imageView's width and height to the scrollView's width and height
theImageView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor, multiplier: 1.0).isActive = true
theImageView.heightAnchor.constraint(equalTo: theScrollView.heightAnchor, multiplier: 1.0).isActive = true
// set the imageView's top / bottom / leading / trailing anchors
// this *also* determines the scrollView's contentSize (scrollable area)
// with 100-pt padding on each side
theImageView.topAnchor.constraint(equalTo: theScrollView.topAnchor, constant: 100.0).isActive = true
theImageView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor, constant: -100.0).isActive = true
theImageView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor, constant: 100.0).isActive = true
theImageView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor, constant: -100.0).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// set the scrollView's contentOffset (to center the imageView)
theScrollView.contentOffset = CGPoint(x: 100, y: 100)
}
}
You can move only down and right because your current content offset is 0,0 so top left - thus you can move down 200 and right 200.
What you want is to be scrolled 1/2 of vertical padding and 1/2 of horizontal padding, so in your case you would do scrollView.contentOffset = CGPoint(x: 100, y: 100)
Also for everything to work, UIImageView has to be same size as scrollView's contentSize, so bigger than screen size.
Given the comments what I think you want is the image to fill the screen and then user could scroll outside of bounds of the image, then you just need to make UIImageView's size be size of the screen its x and y coordinates to be same as contentOffset of the scrollView so (100, 100).
Here is the video of the sample app doing this:
https://dzwonsemrish7.cloudfront.net/items/2v361r2p0O2j1D3x3W10/Screen%20Recording%202018-04-19%20at%2002.32%20PM.mov
try this in
Swift 4.* or 5.*
let maxScale = self.imageScrollView.maximumZoomScale
let minScale = self.imageScrollView.minimumZoomScale
if let imageSize = imageView.image?.size{
let topOffset: CGFloat = (boundsSize.height - minScale * imageSize.height ) / 2
let leftOffset: CGFloat = (boundsSize.width - minScale * imageSize.width ) / 2
self.imageScrollView.contentInset = UIEdgeInsets(top: topOffset, left: leftOffset, bottom: 0, right: 0)
}

Adding a UILabel to a subview is making the view it disappear

I am trying to create the following view heirarchy
Base UIView-> UIScrollView -> UIView -> UILabel
In the Storyboard I created the view->UIScrollView and made the Scrollview and the base view the same size and aligned their origins.
I am then adding the subsequent UIView -> UILabels in code.
In the ViewController viewDidLoad() function I am adding the UIView and the UILabel and also setting contraints on the UIScrollView.
I am having trouble adding the UILabel. If I just add the UIView to the scrollView its working fine (I gave them different colors just to see that).
As soon as I add the UILabel to the view I see the following issues --
the UIView seems to be disappearing and the label is getting added to the base View.
The UILabel is always getting added to the top corner of the screen. No matter what I do its not changing.
I have a feeling these two issues are connected and I am not adding the constraints on the label properly.
It will be great if someone can point out the error in my code.
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.orange
let boxViewOrigin = CGPoint(x:0, y:0)
let boxViewSize = CGSize(width: view.bounds.width * 3, height: view.bounds.height * 3)
let boxView = UIView(frame: CGRect(origin: boxViewOrigin, size: boxViewSize))
boxView.backgroundColor = UIColor.blue
boxView.translatesAutoresizingMaskIntoConstraints = false
let scrollView = view.subviews[0] as! UIScrollView
scrollView.addSubview(boxView)
// Add contraints
scrollView.contentSize = boxViewSize //CGSize(width: view.bounds.width * 3, height: view.bounds.height * 3)
scrollView.contentOffset = CGPoint(x: (view.bounds.origin.x + view.bounds.width) , y: (view.bounds.origin.y + view.bounds.height) )
/// ---- Commenting out everything from here to the end of the function makes the UIView appear properly. ------
let nameLabel = UILabel()
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.backgroundColor = UIColor.yellow
let nameLabelOrigin = CGPoint(x: boxView.bounds.midX, y: boxView.bounds.midY )
nameLabel.frame = CGRect(origin: nameLabelOrigin, size: CGSize(width: 30, height: 30) )
nameLabel.text = "BB"
//nameLabel.isEnabled = false
nameLabel.isUserInteractionEnabled = false
let verticalConstraint:NSLayoutConstraint = NSLayoutConstraint(item: nameLabel, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: boxView, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)
let horizontalConstraint:NSLayoutConstraint = NSLayoutConstraint(item: nameLabel, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: boxView, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
boxView.addSubview(nameLabel)
view.addConstraints([verticalConstraint, horizontalConstraint])
}

When typing long texts, textview appends empty spaces proportional to the input

the empty space on the endI am taking the size of my text view text (the text view that I am writing on) and setting it equal to the size of the text view that I am displaying it. But it if write longer text it shows longer empty space in my text view when I post it. I made the code for the size of my text view by measuring the content.height of the textV that i AM writing on.
} else if identifier == "save" {
print("Save button tapped")
if textViewWrite.text == "" {
print("text empty")
}
else{
print("\(textViewWrite.contentSize.height)")
let size: CGSize = textViewWrite.sizeThatFits(CGSize.init(width: textViewWrite.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
let insets: UIEdgeInsets = textViewWrite.textContainerInset;
let relevantHeight = size.height - insets.top - insets.bottom;
let text = Post(texts: "String", textHeight: relevantHeight, textWidth: textViewWrite.contentSize.width)
PostService.create(text: self.textViewWrite.text!, height: Int(textViewWrite.contentSize.height), width: Int(view.frame.width) )
print("\(textViewWrite.contentSize.height)")
// 1
let HomeViewController = segue.destination as! HomeViewController
// 2
HomeViewController.posts.append(text)
}
textViewWrite.text = ""
}
}
I have seen the method you are using used before, however for your purposes, I think it's best to use layout constraints. It's a lot easier in my opinion.
What you want to do is use layout constraints to constrain the height property of the receiving text view.
In your receiving text view try setting up layout constraints like so...
let captionContainer: UIView = {
let c = UIView()
c.translatesAutoresizingMaskIntoConstraints = false
c.backgroundColor = UIColor.lightGray
c.layer.cornerRadius = 6
return c
}()
let caption: UITextView = {
let c = UITextView()
c.textAlignment = .left
c.font = .systemFont(ofSize: 14)
c.isScrollEnabled = false
c.translatesAutoresizingMaskIntoConstraints = false
return c
}()
func setupCaption() {
view.addSubview(captionContainer)
view.addSubview(caption)
//Container Constratins
captionContainer.widthAnchor.constraint(equalToConstant: 250).isActive = true
captionContainer.heightAnchor.constraint(equalTo: caption.heightAnchor, multiplier: 1).isActive = true
captionContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
captionContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
//Caption Constraints
caption.widthAnchor.constraint(equalToConstant: 250).isActive = true
caption.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
caption.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// This constraint controls the maximum height the caption can have.
NSLayoutConstraint(item: caption, attribute: .height, relatedBy: .lessThanOrEqual, toItem: view, attribute: .height, multiplier: 0.25, constant: 0).isActive = true
}
Then set the caption text to whatever the textfield or textview you are using for user input.
Good luck.

Adding constraints to UITableViewCell contentView

I am trying to add constraints to tableViewCellSubViews, like so -
import UIKit
class SnakeTableViewCell: UITableViewCell {
var lessonViews = Array<UIView>()
override func awakeFromNib() {
super.awakeFromNib()
for var i = 0; i < 3; ++i
{
var view = UIView(frame: CGRectMake(CGFloat(i) * 110.0, 0.0, 100.0, 100.0))
view.backgroundColor = UIColor.redColor()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
self.contentView.addSubview(view)
lessonViews.append(view)
}
self.addConstraints(NSLayoutConstraint.constraintsForEvenDistributionOfViews(lessonViews, relativeToCenterOfView: self, vertically: false))
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
And the constraints code -
extension NSLayoutConstraint {
class func constraintsForEvenDistributionOfViews(views:Array<UIView>,relativeToCenterOfView toView:UIView, vertically:Bool ) -> Array<NSLayoutConstraint> {
var constraints = Array<NSLayoutConstraint>()
let attribute = vertically ? NSLayoutAttribute.CenterY : NSLayoutAttribute.CenterX
for (index, view) in enumerate(views) {
let multiplier = CGFloat(2*index + 2) / CGFloat(views.count + 1)
let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: NSLayoutRelation.Equal, toItem:toView, attribute: attribute, multiplier: multiplier, constant: 0)
constraints.append(constraint)
}
return constraints
}
}
The issue is that when I add the constraints, all the subviews disappears.
Any idea what am I doing wrong ?
Thanks
The multiplier generally is 1. The constant is the variable (horizontal or vertical amount.
You can setup your custom cell, its evenly distributed subViews, and its constraints, all within Storyboard. It's much simpler to do it in Interface Builder, than creating and constraining views in code.
You simply dequeue a reusable cell, and it's already got its properly spaced views, because the cell instantiated and spaced them for you.
You can add constraints to the contentView of a UITableViewCell in init(style:, reuseIdentifier:) as follows (in this case adjusting with an inset of 5pt):
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 5).isActive = true
contentView.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
contentView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -5).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5).isActive = true
It works, but... the system will complain:
Changing the translatesAutoresizingMaskIntoConstraints property of the contentView of a UITableViewCell
is not supported and will result in undefined behavior
The warning appears only one time (even though I have multiple cells)
I am doing this to add an inner border and a shadow (without using additional views that will affect the smooth scroll). But the 'not supported' and 'undefined behavior' are things you may consider if you want to use this in a production environment.

Resources