I have a custom UIView class where I'm creating a UIView and putting inside UILabel. Initially, I set UIView height equal to 60.0. But how can I do that my UIView height would resize with UILabel? For example if UILabel contains 5 linesof text UIView height will also increase to 200pt(for example).
Here is my class: https://gist.github.com/orkhanalizade/747dc4fd1eb9f228ac964fb4048125dc
I have tried
self.translatesAutoresizingMaskIntoConstraints = false
self.heightAnchor.constraint(greaterThanOrEqualToConstant: 60.0).isActive = true
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 60.0).isActive = true
but it did not help me
What I do wrong and how can I fix it?
Since you are resizing the constants, you may want to use layoutIfNeeded(). It will force update the constraints in the view. You can read more about it in Apple's documentation.
Just calculate the height according to text and update height constraint of UIView with UIView.animate() method.
Couple notes...
1) In general, a view should not set its own frame. What happens if you want to add MyView as a subview of another view? Setting its frame as you have:
width: UIScreen.main.bounds.width - 16.0
will not give you the desired results.
2) You do not need:
self.addConstraints(constraints)
3) I find it helpful to give elements different, obvious background colors -- makes it easy to see what the frames are doing.
Here is an edited version of your gist, along with a view controller to add / display it:
import UIKit
class MyView: UIView {
var label: UILabel!
var text: String? {
didSet {
label.text = text
}
}
var cornerRadius: CGFloat = 0.0 {
didSet {
self.layer.cornerRadius = self.cornerRadius
self.layer.masksToBounds = true
}
}
var textColor: UIColor = UIColor.black {
didSet {
label.textColor = textColor
}
}
var isTextCentered: Bool = false {
didSet {
self.label.textAlignment = isTextCentered ? .center : .left
}
}
init() {
// we'll be using constraints, so no need to set a frame
super.init(frame: CGRect.zero)
initialize()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func initialize() {
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
self.addSubview(label)
let constraints = [
NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: label, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: -8.0),
NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -8.0)
]
NSLayoutConstraint.activate(constraints)
// so we can see self's frame
self.backgroundColor = .red
// so we can see label's frame
label.backgroundColor = .yellow
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// instantiate the custom view
let v = MyView()
// we'll be using constraints
v.translatesAutoresizingMaskIntoConstraints = false
// add the view
view.addSubview(v)
NSLayoutConstraint.activate([
// constrain Top: 60 / Leading: 8 / Trailing: -8
v.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 60.0),
v.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 8.0),
v.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -8.0),
// constrain height >= 60
v.heightAnchor.constraint(greaterThanOrEqualToConstant: 60.0),
])
// add 10 lines of text
v.text = (1...10).map({ "Line \($0)" }).joined(separator: "\n")
}
}
And the result:
Related
I would like to create a custom UIView, say TestLabelView, which contains a label whose margin would be 16px.
It would look like this:
Here is how I tried to create this custom UIView and instantiated it in a view controller:
TestLabelView
class TestLabelView: UIView {
private var label = UILabel()
var text: String = "" { didSet { updateUI() } }
override init(frame: CGRect) {
super.init(frame: frame)
internalInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
internalInit()
}
private func internalInit() {
backgroundColor = .red
label.numberOfLines = 0
translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
}
private func updateUI() {
label.text = text
NSLayoutConstraint(item: label,
attribute: .leading,
relatedBy: .equal,
toItem: self,
attribute: .leading,
multiplier: 1,
constant: 24).isActive = true
NSLayoutConstraint(item: self,
attribute: .trailing,
relatedBy: .equal,
toItem: label,
attribute: .trailing,
multiplier: 1,
constant: 24).isActive = true
NSLayoutConstraint(item: label,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1,
constant: 24).isActive = true
NSLayoutConstraint(item: self,
attribute: .bottom,
relatedBy: .equal,
toItem: label,
attribute: .bottom,
multiplier: 1,
constant: 24).isActive = true
setNeedsUpdateConstraints()
}
}
ViewController
class ViewController: UIViewController {
#IBOutlet weak var testLabelView: TestLabelView?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
testLabelView?.text = "This is a test view with a test label, contained in multiple lines"
}
}
In the storyboard
I have set a placeholder intrinsic size to remove the storyboard error, and the bottom margin is greater than or equal to 24px, so the view height should be set depending on the content size
However by doing so, I don't see the label appearing. What am I doing wrong?
Thank you for your help
You can try this code for your TestLabelView view
class TestLabelView: UIView {
private var label : UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.numberOfLines = 0
return l
}()
var text: String = "" { didSet { self.label.text = self.text } }
override init(frame: CGRect) {
super.init(frame: frame)
internalInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
internalInit()
}
private func internalInit() {
backgroundColor = .red
translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
setConstraints()
}
private func setConstraints() {
NSLayoutConstraint.activate([
self.label.topAnchor.constraint(equalTo: self.topAnchor, constant: 16),
self.label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
self.label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16),
self.label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -16),
])
}
}
I have a UITextField that is in a specific view with constraints. It works flawlessly if the text is not a single word. But as you can see it in the above image, if the text is only one word, it does not adjust the font size to fit. How can I fix it? Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
lazy var textInput : UITextField = {
let textInput = UITextField()
textInput.textAlignment = .center
textInput.translatesAutoresizingMaskIntoConstraints = false
textInput.minimumFontSize = 10;
textInput.adjustsFontSizeToFitWidth = true;
textInput.backgroundColor = .yellow
myView.addSubview(textInput)
var lConst = NSLayoutConstraint(item: textInput, attribute: .leading, relatedBy: .equal, toItem: myView, attribute: .leading, multiplier: 1, constant: 0)
var toConst = NSLayoutConstraint(item: textInput, attribute: .top, relatedBy: .equal, toItem: myView, attribute: .top, multiplier: 1, constant: 0)
var trConst = NSLayoutConstraint(item: textInput, attribute: .trailing, relatedBy: .equal, toItem: myView, attribute: .trailing, multiplier: 1, constant: 0)
var bConst = NSLayoutConstraint(item: textInput, attribute: .bottom, relatedBy: .equal, toItem: myView, attribute: .bottom, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([lConst,toConst,trConst,bConst])
return textInput
} ()
override func viewDidLoad() {
super.viewDidLoad()
_ = textInput
textInput.font = UIFont.boldSystemFont(ofSize: 200)
// Do any additional setup after loading the view.
}
}
If you mean "character" instead of "word" (looking to your images), I think your problem is, that by default the first character is uppercase. If you want to start your text with lowercase character you have to set this:
textInput.autocapitalizationType = .none
Edit: Sorry I just misinterpreted your images and don't saw, that your letter is cut at top and bottom.
I wrote your code with a little bit different layout syntax and it works fine, even when the view is smaller than your font size.
class ViewController: UIViewController {
lazy var myView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var textInput : UITextField = {
let textInput = UITextField()
textInput.textAlignment = .center
textInput.translatesAutoresizingMaskIntoConstraints = false
textInput.minimumFontSize = 10;
textInput.adjustsFontSizeToFitWidth = true;
textInput.backgroundColor = .yellow
return textInput
} ()
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
// Do any additional setup after loading the view.
}
func setupLayout(){
view.addSubview(myView)
NSLayoutConstraint.activate([
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
myView.widthAnchor.constraint(equalToConstant: 180),
myView.heightAnchor.constraint(equalToConstant: 180)
])
textInput.font = UIFont.boldSystemFont(ofSize: 200)
myView.addSubview(textInput)
NSLayoutConstraint.activate([
textInput.topAnchor.constraint(equalTo:myView.topAnchor),
textInput.trailingAnchor.constraint(equalTo: myView.trailingAnchor),
textInput.bottomAnchor.constraint(equalTo: myView.bottomAnchor),
textInput.leadingAnchor.constraint(equalTo: myView.leadingAnchor)
])
}
}
I've created a custom View that has subviews added to it and a UIPanGestureRecognizer is added to each UIView object but when I select the UIView after I update the constraints there is a delay. If I drag the UIView (sView) off the screen, the next time I drag the view in either direction there is a delay of a few seconds before the pan gesture recognizer registers. It doesn't register the pan gesture immediately.
I've read up on auto layout and believe its configured correctly in the animation callback method. I've created a delegate in the view controller that has access to the updated constraints, and sView.leftConstraint and sView.rightConstraint are fields I have added to a custom view that represents a draggable UIView.
The ScrollViewController adopts a protocol so that it can access the view that was selected and update the constraints accordingly.
Any help would be greatly appreciated :)
extension ScrollViewController: DragDelegate {
func draggedLeft(sView: DraggableView) {
print("Dragged Left")
confirmationLabel.text = ""
sView.selectionLabel.text = "Not Selected"
sView.cornerRadius = 7
sView.rightConstraint?.isActive = false
sView.leftConstraint?.isActive = false
sView.leftConstraint?.constant = 1
sView.leftConstraint?.isActive = true
// sView.widthConstraint?.constant = 300
UIView.animate(withDuration: 0.3) {
sView.updateConstraints()
self.contentView.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
}
class OrderSelectionView: UIView {
var dView : DraggableView?
var dView2 : DraggableView?
let screenWidth = UIScreen.main.bounds.width
//I need to set up all the constraints in this class
//This will act almost like a container view
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
let margins = self.layoutMarginsGuide
self.translatesAutoresizingMaskIntoConstraints = false
dView = DraggableView(frame: frame)
dView2 = DraggableView(frame: frame)
guard let dragView = dView else {return}
guard let dragView2 = dView2 else {return}
dragView.translatesAutoresizingMaskIntoConstraints = false
dragView.topAnchor.constraint(equalTo: laundryLabel.bottomAnchor, constant: 10).isActive = true
let rightAnchor = NSLayoutConstraint(item: dragView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
let leftAnchor = NSLayoutConstraint(item: dragView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
dragView.widthAnchor.constraint(equalToConstant: 150).isActive = true
dragView.heightAnchor.constraint(equalToConstant: 40).isActive = true
dragView.leftConstraint = leftAnchor
dragView.leftConstraint?.isActive = true
dragView.rightConstraint = rightAnchor
self.addSubview(dragView2)
dragView2.translatesAutoresizingMaskIntoConstraints = false
let laundryLabel2 = createLaundryLabel(lTitle: "Dry", constant: 100)
dragView2.translatesAutoresizingMaskIntoConstraints = false
dragView2.topAnchor.constraint(equalTo: laundryLabel2.bottomAnchor, constant: 10).isActive = true
let rightAnchor2 = NSLayoutConstraint(item: dragView2, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: -50)
let leftAnchor2 = NSLayoutConstraint(item: dragView2, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
dragView2.widthAnchor.constraint(equalToConstant: 150).isActive = true
dragView2.heightAnchor.constraint(equalToConstant: 40).isActive = true
dragView2.leftConstraint = leftAnchor2
dragView2.leftConstraint?.isActive = true
dragView2.rightConstraint = rightAnchor2
}
I want to create an array of objects that are horizontally cascaded, I have attempted to create a function to reduce the size of my code however, it seems like there might be an NSLayoutConstraint conflict between the objects that are created maybe?
Here's my code
private func createProfileImageContainers(numberOfFriends: Int) {
for friends in 1...numberOfFriends {
let imageViewContainer = UIView()
imageViewContainer.translatesAutoresizingMaskIntoConstraints = false
imageViewContainer.backgroundColor = UIColor.blue
imageViewContainer.frame = CGRect(x: 0, y: 0, width: frame.width / 10, height: frame.width / 10)
NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: CGFloat((1 / 2) + ((friends - 1) / 50 )), constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .centerY, relatedBy: .equal, toItem: container, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
addSubview(imageViewContainer)
}
}
Here's what debugger is saying
A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs.'
Any suggestions?
EDIT:
Thanks to Robs answer I was able to solve the issues with the debugger however only one instance of imageViewContainer is being adding. Probably because they are all being added to the view hierarchy with the same name so each new view takes the place of the last...
I thought creating a class would solve this but now I can't get anything to appear.
Here's the updated code...
class profileImageContainer: UIView {
let imageViewContainer: UIView = {
let iv = UIView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.backgroundColor = UIColor.blue
return iv
}()
}
private func createProfileImageContainers(numberOfFriends: Int) {
for friends in 1...numberOfFriends {
print(friends)
let imageViewContainer = profileImageContainer()
addSubview(imageViewContainer)
NSLayoutConstraint(item: imageViewContainer, attribute: .width, relatedBy: .equal, toItem: container, attribute: .width, multiplier: 0.1, constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .height, relatedBy: .equal, toItem: container, attribute: .width, multiplier: 0.1, constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: 0.5 + (CGFloat(friends - 1) / 50.0), constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .centerY, relatedBy: .equal, toItem: container, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
}
}
One problem is the expression:
CGFloat((1 / 2) + ((friends - 1) / 50))
That is doing integer division and then converting the resulting integer into a CGFloat. In practice, your expression will return 0 for the first 50 friends values.
You want to do floating point math, converting friends - 1 to a CGFloat before you do the division:
0.5 + CGFloat(friends - 1) / 50.0
I'd also suggest that you'll want to add the subview before adding the constraints.
You should specify the width and height constraints and eliminate the setting of the frame. The frame will be discarded when the constraints are applied and in the absence of width and height constraints, the constraints are ambiguous.
There are a couple of problems with your second code sample:
Your ProfileImageContainer has an imageViewContainer, but you never do anything with it. So, you're not going to see your ProfileImageContainers (because you didn't set its own backgroundColor). I can imagine that you might eventually do something meaningful with imageViewContainer property of ProfileImageContainer (e.g. add it to the view hierarchy, set the image, etc.). But for now, I'd suggest you remove that as it's only confusing the situation.
Even when we fix the above, the subviews are going to overlap because you've defined them to be 1/10th of the width of some container, but you're adjusting the centerX multiplier by 1/50th.
The net effect of this is that the views will overlap, making it appear that there is only one present. But I believe if you use the view debugger, you’ll see that they’re all there. You need to alter the centerX constraint so that they don’t overlap.
Anyway, here is a rendition that fixes the above issues:
// SampleView.swift
import UIKit
class ProfileImageContainer: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .blue
}
}
class SampleView: UIView {
var container: UIView!
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
addSubview(container)
NSLayoutConstraint.activate([
container.leftAnchor.constraint(equalTo: leftAnchor),
container.rightAnchor.constraint(equalTo: rightAnchor),
container.topAnchor.constraint(equalTo: topAnchor),
container.bottomAnchor.constraint(equalTo: bottomAnchor)
])
createProfileImageContainers(numberOfFriends: 5)
}
var friends = [UIView]()
private func createProfileImageContainers(numberOfFriends: Int) {
// remove old friends in case you called this before
friends.forEach { $0.removeFromSuperview() }
friends.removeAll()
// now add friends
for friend in 0 ..< numberOfFriends { // easier to go from 0 to numberOfFriends-1 than subtract one later
print(friend)
let imageViewContainer = ProfileImageContainer()
container.addSubview(imageViewContainer)
NSLayoutConstraint.activate([
imageViewContainer.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.1),
imageViewContainer.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.1),
NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: 2 * CGFloat(friend + 1) / CGFloat(numberOfFriends + 1), constant: 0),
imageViewContainer.centerYAnchor.constraint(equalTo: container.centerYAnchor)
])
friends.append(imageViewContainer)
}
}
}
I'm experiencing a weird bug with the appearance of my inputAccessoryView. While in the middle of a transition, it appears like so:
After the transition, it appears as it should:
I override the property like so:
override var inputAccessoryView: UIView! {
get {
if composeView == nil {
composeView = CommentComposeView(frame: CGRectMake(0, 0, 0, MinimumToolbarHeight - 0.5))
self.setupSignals()
}
return composeView
}
}
I'm wondering if anyone can point out any obvious flaw in what I'm doing or provide some more information on how to ensure my view appears as it should, before, during, and after transitions.
Thanks!
EDIT
Here's my CommentComposeView:
import UIKit
class CommentComposeView: UIToolbar {
var textView: SAMTextView!
var sendButton: UIButton!
private var didSetConstraints: Bool = false
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
private func initialize() {
textView = SAMTextView(frame: CGRectZero)
sendButton = UIButton.buttonWithType(.System) as UIButton
self.barStyle = .Black
self.translucent = true
textView.backgroundColor = UIColor.presentOffWhite()
textView.font = UIFont.presentLightMedium()
textView.layer.borderWidth = 0.5
textView.layer.cornerRadius = 5
textView.placeholder = "Comment"
textView.scrollsToTop = false
textView.textContainerInset = UIEdgeInsetsMake(4, 3, 3, 3)
textView.keyboardAppearance = .Dark
textView.keyboardType = .Twitter
self.addSubview(textView)
sendButton = UIButton.buttonWithType(.System) as UIButton
sendButton.enabled = false
sendButton.titleLabel!.font = UIFont.presentBoldLarge()
sendButton.setTitle("Send", forState: .Normal)
sendButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
sendButton.setTitleColor(UIColor.presentCyan(), forState: .Highlighted)
sendButton.setTitleColor(UIColor.presentLightGray(), forState: .Disabled)
sendButton.contentEdgeInsets = UIEdgeInsetsMake(6, 6, 6, 6)
self.addSubview(sendButton)
RAC(self.sendButton, "enabled") <~ self.textView.rac_textSignal()
.map { text in
return (text as NSString).length > 0
}
textView.setTranslatesAutoresizingMaskIntoConstraints(false)
sendButton.setTranslatesAutoresizingMaskIntoConstraints(false)
}
override func updateConstraints() {
super.updateConstraints()
if !didSetConstraints {
// TODO: Replace raw constraints with a friendlier looking DSL
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 8)
)
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 7.5)
)
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Right, relatedBy: .Equal, toItem: sendButton, attribute: .Left, multiplier: 1, constant: -2)
)
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: -8)
)
self.addConstraint(
NSLayoutConstraint(item: sendButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0)
)
self.addConstraint(
NSLayoutConstraint(item: sendButton, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: -4.5)
)
}
}
}
This is iOS8 issue with inputAccessoryView autolayout. Issue is that UIToolbar's subview of clas _UIToolbarBackground is not positioned properly during initial layout. Try to do next things:
Make CommentComposeView subclassing UIView, not UIToolbar, add instance of UIToolbar as subview.
Use autolayout masks (not actual constraints) inside your CommentComposeView
Override -layoutSubviews in your CommentComposeView like this:
- (void)layoutSubviews
{
[super layoutSubviews];
contentToolbar.frame = self.bounds;
sendButton.frame = CGRectMake(0.f, 0.f, 44.f, self.bounds.size.height);
textView.frame = CGRectMake(44.f, 0.f, self.bounds.size.width - 44.f, self.bounds.size.height);
}