I want to set label & Imageview in centre of myView but App crashing after adding some constraints - ios

Error:-
Thread 1: "Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x280af8500 \"UILabel:0x103dc4fa0.centerX\"> and <NSLayoutXAxisAnchor:0x280af89c0 \"UIView:0x103dc49d0.centerX\"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal."
I've created a function programmatical in Base view controller which returns a view & I've added some constraints to its
Function:-
func getMarker (lbl:String, img:UIImage) -> UIView {
let myView = UIView(frame: CGRect.zero)
myView.center = CGPoint(x: 50, y: 160)
myView.backgroundColor = UIColor.clear
let imageView = UIImageView(image: img)
imageView.frame = CGRect(x: 0, y: 0, width: 20, height: 40)
myView.addSubview(imageView)
let label = UILabel(frame: CGRect(x: 0, y: 45, width: 120, height: 30))
label.text = lbl
label.textAlignment = .center
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor.black
label.backgroundColor = UIColor.white
label.layer.borderColor = UIColor.lightGray.cgColor
label.layer.borderWidth = 0.5
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
label.sizeToFit()
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor)
])
myView.addSubview(label)
return myView
}
calling function in another controller but it crashing and showing me the error which I mentioned above
Calling function:-
getMarker(lbl: device.name ?? "", img: (UIImage(named: icfile) ?? UIImage(named: "truck_1_orange")!))

U need to add subview first, then activate layout. Label is not in myView subviews in your code. It is not in any hierarchy at the moment of layout constraint activation.
myView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor)
])

You need to configure the layout of your subviews (imageView and label) in order to have them sized and positioned where you want them.
Take a look at this example code:
class MarkerVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
guard let img = UIImage(named: "img80x80")
else {
fatalError("Could not load sample image!!!")
}
let markerView1 = getMarker(lbl: "Testing", img: img)
let markerView2 = getMarkerAutoSized(lbl: "Testing", img: img)
markerView1.translatesAutoresizingMaskIntoConstraints = false
markerView2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(markerView1)
view.addSubview(markerView2)
// respect safe area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// we have to set both Position and Size constraints
// for markerView1
markerView1.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 20.0),
markerView1.widthAnchor.constraint(equalToConstant: 240.0),
markerView1.heightAnchor.constraint(equalToConstant: 100.0),
markerView1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// markerView2 uses its subviews to set its own size
// so we only need position constraints
markerView2.topAnchor.constraint(equalTo: markerView1.bottomAnchor, constant: 20.0),
markerView2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
// so we can see the view frames
markerView1.backgroundColor = .systemRed
markerView2.backgroundColor = .systemRed
}
func getMarker (lbl:String, img:UIImage) -> UIView {
let myView = UIView()
myView.backgroundColor = UIColor.clear
let imageView = UIImageView(image: img)
let label = UILabel()
label.text = lbl
label.textAlignment = .center
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor.black
label.backgroundColor = UIColor.white
label.layer.borderColor = UIColor.lightGray.cgColor
label.layer.borderWidth = 0.5
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
myView.addSubview(imageView)
myView.addSubview(label)
NSLayoutConstraint.activate([
// image view Width and Height
imageView.widthAnchor.constraint(equalToConstant: 20.0),
imageView.heightAnchor.constraint(equalToConstant: 40.0),
// labe Width and Height
label.widthAnchor.constraint(equalToConstant: 120.0),
label.heightAnchor.constraint(equalToConstant: 30.0),
// image view aligned to Top
imageView.topAnchor.constraint(equalTo: myView.topAnchor),
// centered Horizontally
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
// label 5-pts below image view
label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5.0),
// centered Horizontally
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
])
return myView
}
func getMarkerAutoSized (lbl:String, img:UIImage) -> UIView {
let myView = UIView()
myView.backgroundColor = UIColor.clear
let imageView = UIImageView(image: img)
let label = UILabel()
label.text = lbl
label.textAlignment = .center
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor.black
label.backgroundColor = UIColor.white
label.layer.borderColor = UIColor.lightGray.cgColor
label.layer.borderWidth = 0.5
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
myView.addSubview(imageView)
myView.addSubview(label)
NSLayoutConstraint.activate([
// image view Width and Height
imageView.widthAnchor.constraint(equalToConstant: 20.0),
imageView.heightAnchor.constraint(equalToConstant: 40.0),
// labe Width and Height
label.widthAnchor.constraint(equalToConstant: 120.0),
label.heightAnchor.constraint(equalToConstant: 30.0),
// image view aligned to Top
imageView.topAnchor.constraint(equalTo: myView.topAnchor),
// centered Horizontally
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
// label 5-pts below image view
label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5.0),
// centered Horizontally
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
// to auto-size myView
label.leadingAnchor.constraint(equalTo: myView.leadingAnchor),
label.trailingAnchor.constraint(equalTo: myView.trailingAnchor),
label.bottomAnchor.constraint(equalTo: myView.bottomAnchor),
])
return myView
}
}
I modified your getMarker(...) func to size and position the subviews.
I also added a very similar getMarkerAutoSized(...) func that uses a couple additional constraints on the subviews.
For the first version, we must set both the Position and Size of the generated view.
For the second version, we only need to set the generated view's Position because it sizes itself to fit its subviews.
Here's how they look:

Related

Update a label's text in a UIStackView and while keeping the text centered?

When I place a label with centered text in a stack view, then update the text, it no longer appears centered.
import PlaygroundSupport
import UIKit
let stackView = UIStackView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 20)))
let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 20)))
label.text = "Hello"
label.textAlignment = .center
stackView.addArrangedSubview(label)
label.text = "World"
PlaygroundPage.current.liveView = stackView
The expected outcome is that "World" is centered. However, it appears leftaligned. Calling layoutSubviews() did not help.
Stack views don't play well without auto-layout - particularly in Playgrounds.
Try it like this:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let stackView = UIStackView()
let label = UILabel()
label.text = "Hello"
label.textAlignment = .center
// so we can see the frame
label.backgroundColor = .green
stackView.addArrangedSubview(label)
label.text = "World"
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.widthAnchor.constraint(equalToConstant: 100.0),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20.0),
])
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

ScrollView not scrolling when label text is big string

I'm trying to create a scroll view programatically, but it is not working as expected. Here is my code.
let scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.leadingAnchor(equalTo: view.leadingAnchor)
scrollView.trailingAnchor(equalTo: view.trailingAnchor)
scrollView.topAnchor(equalTo: view.topAnchor)
scrollView.bottomAnchor(equalTo: view.bottomAnchor)
let imageview = UIImageView()
scrollView.addSubview(imageview)
let contentLabel = UILabel()
scrollView.addSubview(contentLabel)
imageview.leadingAnchor(equalTo: view.leadingAnchor)
imageview.trailingAnchor(equalTo: view.trailingAnchor)
imageview.topAnchor(equalTo: view.topAnchor)
imageview.heightAnchor(equalTo: 300)
imageview.backgroundColor = .cyan
contentLabel.numberOfLines = 0
contentLabel.leadingAnchor(equalTo: view.leadingAnchor)
contentLabel.trailingAnchor(equalTo: view.trailingAnchor)
contentLabel.topAnchor(equalTo: imageview.bottomAnchor, constant: 16)
contentLabel.bottomAnchor(equalTo: scrollView.bottomAnchor, constant: -16)
contentLabel.text = BIG_TEXT
I'm trying to add an image view and text below that. When the text content is more I should make it scrollable. Thanks in advance.!!
Try to add a UIStackView as the first child of your UIScrollView and add your subviews to it with the addArrangedSubview method:
let scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.leadingAnchor(equalTo: view.leadingAnchor)
scrollView.trailingAnchor(equalTo: view.trailingAnchor)
scrollView.topAnchor(equalTo: view.topAnchor)
scrollView.bottomAnchor(equalTo: view.bottomAnchor)
let stackView = UIStackView()
stackView.axis = .vertical
stackView.leadingAnchor(equalTo: scrollView.leadingAnchor)
stackView.trailingAnchor(equalTo: scrollView.trailingAnchor)
stackView.topAnchor(equalTo: scrollView.topAnchor)
stackView.bottomAnchor(equalTo: scrollView.bottomAnchor)
stackView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
let imageview = UIImageView()
imageview.heightAnchor(equalTo: 300)
imageview.backgroundColor = .cyan
stackView.addArrangedSubview(imageview)
let contentLabel = UILabel()
contentLabel.text = BIG_TEXT
contentLabel.numberOfLines = 0
stackView.addArrangedSubview(contentLabel)
stackView.layoutIfNeeded()

Swift - How to set custom view by text length

Here is my code,
func bannerNotification(text: String){
let container = UIView()
let image = UIImageView()
let label = UILabel()
container.frame = CGRect(x: 0, y:0, width: self.view.frame.size.width, height: 100)
container.backgroundColor = .blue
image.frame = CGRect(x: 15, y: 50, width: 30, height: 30)
image.image = UIImage(named: "passport")
label.frame = CGRect(x: image.bounds.maxX + 35, y: 50, width: container.frame.size.width - 100, height: 50)
label.backgroundColor = .red
label.numberOfLines = 0
label.font = UIFont(name:"Helvetica Neue", size: 15)
label.text = text
container.addSubview(image)
container.addSubview(label)
self.view.addSubview(container)
}
According to this code the container and Image is coming on right position but if I pass small text so my text is not inline with Image, means my image top position and text top position should be same.
If I will pass a big text so container bottom and Label bottom should be same and all text should be there not truncated and Image and Label should be inline from the top.
You really want to use auto-layout for this, particularly since you're using multi-line label -- meaning, it will vary in height.
Here's an example - read through the comments to understand the auto-layout constraints:
class KingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
bannerNotification(text: "Banner Test")
//bannerNotification(text: "Banner Test with lots of text to wrap onto multiple lines. Of the many advantages with using auto-layout, notice that the banner will stretch when you rotate the device.")
}
func bannerNotification(text: String){
let container = UIView()
let image = UIImageView()
let label = UILabel()
container.addSubview(image)
container.addSubview(label)
self.view.addSubview(container)
[image, label, container].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
container.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
//image.image = UIImage(named: "passport")
image.image = UIImage(named: "swiftRed")
label.backgroundColor = .yellow
label.numberOfLines = 0
label.font = UIFont(name:"Helvetica Neue", size: 15)
label.text = text
// respect the safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain container Top / Leading / Trailing
container.topAnchor.constraint(equalTo: view.topAnchor),
container.leadingAnchor.constraint(equalTo: view.leadingAnchor),
container.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// we'll use the label height to determine the container height
// image view Top = 8 / Leading = 15
image.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
image.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 15.0),
// image view width = 30 / height == width (1:1 ratio)
image.widthAnchor.constraint(equalToConstant: 30.0),
image.heightAnchor.constraint(equalTo: image.widthAnchor),
// label Top aligned to Top of image view
label.topAnchor.constraint(equalTo: image.topAnchor),
// label Leading == image view Trailing + 20
label.leadingAnchor.constraint(equalTo: image.trailingAnchor, constant: 20.0),
// label Trailing = container Trailing - 15
label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -15.0),
// container bottom should be
// At Least 8-pts below the image view bottom
// AND
// At Least 8-pts below the label bottom
container.bottomAnchor.constraint(greaterThanOrEqualTo: image.bottomAnchor, constant: 8.0),
container.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor, constant: 8.0),
])
}
}
Result, with short text:
Result with long text:
Note that it auto-adjusts when the size changes - such as with device rotation:

How to position rotated UILabels inside a UIStackView with no gap?

I'm trying to create a UIStackView containing UILabels that have been rotated. The UIStackView is positioning the UILabels based on their non-rotated widths, such that there is a large gap when I would expect the UILabels to be directly adjacent. How can I correctly reposition these elements?
Code:
let stackView = UIStackView(frame: canvas.bounds)
stackView.translatesAutoresizingMaskIntoConstraints = false
canvas.addSubview(stackView)
stackView.axis = .horizontal
stackView.topAnchor.constraint(equalTo: canvas.topAnchor, constant: 0).isActive = true
stackView.bottomAnchor.constraint(equalTo: canvas.bottomAnchor, constant: 0).isActive = true
stackView.leftAnchor.constraint(equalTo: canvas.leftAnchor, constant: 0).isActive = true
let label1 = UILabel()
label1.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
label1.text = "THREEEEEEEE"
stackView.addArrangedSubview(label1)
let label2 = UILabel()
label2.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
label2.text = "FOUR"
stackView.addArrangedSubview(label2)
What I'm Seeing:
UI Inspector View:
What I Expect:
Why don't you try to add the views to the stack view first, and then rotate the stack view?
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
let label1 = UILabel()
label1.text = "THREEEEEEEE"
let label2 = UILabel()
label2.text = "FOUR"
let stackView = UIStackView(arrangedSubviews: [label1, label2])
stackView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.axis = .vertical
stackView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 100).isActive = true
I used the view instead of the canvas.

How to size a UIScrollView to fit an unknown amount of text in a UILabel?

I have added a scrollview subview in one of my views, but am having trouble getting it's height to accurately fit the content that the scrollview is showing, which is text in the UILabel. The height needs to be dynamic (i.e. a factor of the text length), because I am instantiating this view for many different text lengths. Whenever I log label.frame.bounds I get (0,0) back. I have also tried sizeToFits() in a few places without much luck.
My goal is to get the scrollview to end when it reaches the last line of text. Also, I am using only programmatic constraints.
A condensed version of my code is the following:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let containerView = UIView()
let label = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
// This needs to change
scrollView.contentSize = CGSize(width: 375, height: 1000)
scrollView.addSubview(containerView)
view.addSubview(scrollView)
label.text = unknownAmountOfText()
label.backgroundColor = .gray
containerView.isUserInteractionEnabled = true
containerView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
containerView.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
}
}
Any help is appreciated.
SOLUTION found:
func heightForLabel(text: String, font: UIFont, lineHeight: CGFloat, width: CGFloat) -> CGFloat {
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.setLineHeight(lineHeight: lineHeight)
label.sizeToFit()
return label.frame.height
}
I found this solution online, that gives me what I need to set the appropriate content size for the scrollView height based on the label's height. Ideally, I'd be able to determine this without this function, but for now I'm satisfied.
The key to UIScrollView and its content size is setting your constraints so that the actual content defines the contentSize.
For a simple example: say you have a UIScrollView with width: 200 and height: 200. Now you put a UIView inside it, that has width: 100 and height: 400. The view should scroll up and down, but not left-right. You can constrain the view to 100x400, and then "pin" the top, bottom, left and right to the sides of the scroll view, and AutoLayout will "auto-magically" set the scrollview's contentSize.
When you add subviews that can change size - either explicitly (code, user interaction) or implicitly - if the constraints are set correctly those changes will also "auto-magically" adjust the scrollview's contentSize.
So... here is an example of what you are trying to do:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let label = UILabel()
let s1 = "1. This is the first line of text in the label. It has words and punctuation, but no embedded line-breaks, so what you see here is normal UILabel word-wrapping."
var counter = 1
override func viewDidLoad() {
super.viewDidLoad()
// turn off translatesAutoresizingMaskIntoConstraints, because we're going to set them
scrollView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
// set background colors, just so we can see the bounding boxes
self.view.backgroundColor = UIColor(red: 1.0, green: 0.7, blue: 0.3, alpha: 1.0)
scrollView.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0)
label.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
// add the label to the scrollView, and the scrollView to the "main" view
scrollView.addSubview(label)
self.view.addSubview(scrollView)
// set top, left, right constraints on scrollView to
// "main" view + 8.0 padding on each side
scrollView.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 8.0).isActive = true
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 8.0).isActive = true
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8.0).isActive = true
// set the height constraint on the scrollView to 0.5 * the main view height
scrollView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.5).isActive = true
// set top, left, right AND bottom constraints on label to
// scrollView + 8.0 padding on each side
label.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0).isActive = true
label.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 8.0).isActive = true
label.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -8.0).isActive = true
label.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -8.0).isActive = true
// set the width of the label to the width of the scrollView (-16 for 8.0 padding on each side)
label.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -16.0).isActive = true
// configure label: Zero lines + Word Wrapping
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = UIFont.systemFont(ofSize: 17.0)
// set the text of the label
label.text = s1
// ok, we're done... but let's add a button to change the label text, so we
// can "see the magic" happening
let b = UIButton(type: UIButtonType.system)
b.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(b)
b.setTitle("Add a Line", for: .normal)
b.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 24.0).isActive = true
b.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
b.addTarget(self, action: #selector(self.btnTap(_:)), for: .touchUpInside)
}
func btnTap(_ sender: Any) {
if let t = label.text {
counter += 1
label.text = t + "\n\n\(counter). Another line"
}
}
}
give top,left,right and bottom constraint to label with containerView.
and
set label.numberOfLines = 0
also ensure that you have given top, left, right and bottom constraint to containerView. this will solve your issue
Set the auto layout constraints from the interface builder as shown in image .
enter image description here
I set the height of UIScrollView as 0.2 of the UIView
Then drag the UIlabel from MainStoryBoard to the view controller.
Add this two lines in viewdidload method.
draggedlabel.numberOfLines = 0
draggedlabel.lineBreakMode = NSLineBreakMode.byWordWrapping

Resources