NSLayoutConstraints programmatically - ios

I want to add constraints to a view programmatically.
This is what I did:
extension UIView {
func bottomToTop(other: UIView) {
self.translatesAutoresizingMaskIntoConstraints = false
other.translatesAutoresizingMaskIntoConstraints = false
let constraint = NSLayoutConstraint(
item: self,
attribute: .bottom,
relatedBy: .equal
toItem: other,
attribute: .top,
multiplier: 1.0,
constant: 0.0
)
superview?.addConstraint(constraint)
constraint.isActive = true
}
}
let label = UILabel()
label.text = "Lenaaaaa"
label.sizeToFit()
label.backgroundColor = .green
let label1 = UILabel()
label1.text = "Lena 2"
label1.sizeToFit()
label1.backgroundColor = .green
let uiView = UIView(frame: frame) (not zero)
uiView.addSubview(label)
uiView.addSubview(label2)
label.bottomToTop(label2)
Why do I end up with this?

Why do I end up with this?
Because your constraints are ambiguous. Once you add even one constraint that affects a view, you must describe that view's position and size in terms of autolayout completely. (And you must stop talking about .frame, as it is now effectively meaningless.)
Thus, you have said only
label.bottomToTop(label2)
But you have not said where the top of label is, where the left of label is, where the left of label2 is, and so on. Thus the autolayout engine throws up its hands in despair.
You could easily have discovered this just by running your app and using the view debugger. It puts up great big exclamation marks telling you what your autolayout issues are.

Related

UIStackView: add arranged views with the same width

I have UIStackView and want add to this view some arranged views... sometimes one, sometimes two, three... etc. UIStackView has full width. I want arranged views to have the same width with alignment left, so that they do not fill full width of UIStackView.
This is what I have:
This is what I want:
Is it possible to do it somehow or do I need to change width of UIStackVIew depending on how many arranged I added?
My code:
// creating views
stackView = {
let v = UIStackView()
v.axis = .horizontal
v.alignment = .center
v.distribution = .fillEqually
v.spacing = 5
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
if let img = chosenImage {
let imageView: UIImageView = {
let l = UIImageView()
l.translatesAutoresizingMaskIntoConstraints = false
l.image = img
return l
}()
self.stackView?.addArrangedSubview(imageView)
imageView.addConstraint(NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: imageView, attribute: .height, multiplier: 1, constant: 1))
imageView.setNeedsUpdateConstraints()
} else {
print("Something went wrong")
}
on storyboard, Select stackview, Attributes inspector -> Stackview -> Distribution, select Fill Equally.
You can add an empty clear view to your StackView at the end and set the hugging and resistance priority for this View. It should resize to available space so hugging priority of "fill view" has to be higher and resistance for "fill view" should be low, on your real items set resistance to higher values

How to add interactive UILabels on top of a UIImageView?

I need to add few labels on top of an UIImageView. The labels' text can be changed by tapping on them. What is the best way to achieve this? I am using Swift programming language. Looking up some solutions on stackoverflow, I found a couple of walkthroughs that use String.drawInRect method to draw some text in a rectangle which is then placed on the UIImageView. But like this I don't think I will be able to change the text, or even recognize a touch event on them. Please help.
UPDATE
My code so far:
override func viewDidLoad() {
super.viewDidLoad()
let img = UIImage(named: "Image")
let imgView = UIImageView(image: img)
self.view.addSubview(imgView)
var myLabel = UILabel()
myLabel.text = "Hello There"
myLabel.textColor = UIColor.redColor()
myLabel.font = UIFont(name: "Marker Felt", size: 20)
myLabel.accessibilityIdentifier = "this is good!"
myLabel.frame = CGRect(x: img!.size.width/2 /* - myLable.width / 2 ? */, y: 0, width: img!.size.width, height: 40)
imgView.addSubview(myLabel)
imgView.userInteractionEnabled = true
myLabel.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: "handlePanGesture:")
myLabel.addGestureRecognizer(tapGesture)
}
func handlePanGesture(sender: UITapGestureRecognizer) {
var senderView = sender.view as! UILabel
print(senderView.text)
senderView.text = "look how i changed!"
print(senderView.accessibilityIdentifier)
}
So far the results are positive I have an image with the label on top of it that can respond to touch events. Now I need to find the label's width so that I can effectively place it in the center when required. Then I need to find a way to place the labels at exact coordinates relative to the image's top left corner as origin.
Any help in these two tasks will be hugely appreciated.
Adding label on ImageView is best approach. but you can also do it by adding button on ImageView.
I created a example where i created a ImageView on storyboard and create its outlet in ViewController class and in viewDidLoad i created a label and add it to label and add UITapGestureRecognizer to label. when user taps label we changed the label text and it's position.
class ViewController: UIViewController {
#IBOutlet weak var winterImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(x: 10, y: 0, width: self.winterImageView.frame.width - 10, height: 30))
label.textColor = UIColor.redColor()
label.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
label.addGestureRecognizer(tapGesture)
label.text = "Is Winter is coming, My Friend?"
self.winterImageView.addSubview(label)
}
Change label text and position in handleTap
/// handle tap here
func handleTap(sender: UITapGestureRecognizer) {
let senderView = sender.view as! UILabel
senderView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: senderView, attribute: .CenterX, relatedBy: .Equal, toItem: self.winterImageView, attribute: .CenterX, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: senderView, attribute: .CenterY, relatedBy: .Equal, toItem: self.winterImageView, attribute: .CenterY, multiplier: 1, constant: 0).active = true
print(senderView.text)
senderView.text = "Yes!!! Winter is coming, My Friend!!"
}
You can download project from here InteractiveLabel
I can see from the other answers and comments related to one another virtually same stuff.If you familiar using Cocoa pods then you will agree with my opinion.Always,Just look around yourself and pick the best.If you want your project goes smooth and steady then JLStickerTextView is your friend and its way to go.It's free,elegant and more vibrant label customisation project available to everyone and the best thing about this project is written in handy Swift.
Github Link: https://github.com/luiyezheng/JLStickerTextView
Features
You can add multiple Text to StickerTextView at the same time
Multiple line Text support
Rotate, resize the text with one finger
Set the Color, alpha, font, alignment, TextShadow, lineSpacing...... of the text
StickerTextView also handle the process of rendering text on Image
Written in Swift
Note: In, My personal opinion.Way, the code been written in this projects simply superb and properly categorised.
Avaliable Text Attributes Reference:
MainView Screenshot from the project:
Output from my personal project based on JLStickerTextView.I, hope you will consider it.If you need any more information let me know...
github.com/khush004/StickerView/tree/master
here is code of JLStickerTextView which is error free with compatibility of swift 3.0
You can use a label and add a gesture recognizer from which you can set an action.
EDIT (responding to OP comment) :
Basically you put an UILabel on top of your card, set a gesture recognizer on it, and set a hidden UITextField at the same position as your label. This way when you tap on it, you specify in your gesture recognizer method that the UI must set label as hidden and textfield as visible. When you're done (end editing), just save your changes and update the UI.
If you just want to center align your UILabel and UIImageView, you can use AutoLayout constraint.
NSLayoutConstraint(item: label, attribute: .CenterX, relatedBy: .Equal, toItem: imageView, attribute: .CenterX, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: imageView, attribute: .CenterY, multiplier: 1, constant: 0).active = true
func handlePanGesture(sender: UITapGestureRecognizer) {
let senderView = sender.view as! UILabel
print(senderView.text)
senderView.textColor = UIColor.redColor()
senderView.text = "look how i changed!"
print(senderView.accessibilityIdentifier)
}
Ouput :
sender.view?.frame
▿ Optional
▿ Some : CGRect
▿ origin : CGPoint
- x : 0.0
- y : 0.0 { ... }
▿ size : CGSize
- width : 335.0
- height : 28.0
I use CALayers, gesture recognizers, and the hitTest method of the layers. Sample code below:
class ImageView: UIImageView {
let tapGesture = UITapGestureRecognizer()
let redLayer = CATextLayer()
var redHitCounter:Int = 0
let greenLayer = CATextLayer()
var greenHitCounter:Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
setUpClickableLayers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpClickableLayers()
}
private func setUpClickableLayers() {
self.isUserInteractionEnabled = true
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(changeText))
self.addGestureRecognizer(tapGesture)
redLayer.frame = CGRect(x: 40, y: 40, width: 100, height: 40)
redLayer.backgroundColor = UIColor.red.cgColor
redLayer.string = String(redHitCounter)
redLayer.alignmentMode = kCAAlignmentCenter
self.layer.addSublayer(redLayer)
greenLayer.frame = CGRect(x: 40, y: 140, width: 100, height: 40)
greenLayer.backgroundColor = UIColor.green.cgColor
greenLayer.string = String(redHitCounter)
greenLayer.alignmentMode = kCAAlignmentCenter
self.layer.addSublayer(greenLayer)
}
internal func changeText(_ recognizer:UITapGestureRecognizer) {
let p = recognizer.location(in: self)
if (redLayer.hitTest(p) != nil) {
redHitCounter += 1
redLayer.string = String(redHitCounter)
} else if (greenLayer.hitTest(p) != nil) {
greenHitCounter += 1
greenLayer.string = String(greenHitCounter)
}
}
}
A few notes:
(1) Remember to set your UIImageView's isUserInteractionEnabled to true. It took me an hour to debug why my UIImageView was seeing gestures!
(2) The hitTest() method works for the CALayer and all subclasses. Just remember to make the layer large enough to work on fat fingers.
(3) You can also use the pan and pinch gestures to move, rotate, and resize the target layer.

Swift - Xcode 8 - iOS 10 - Can't create simple constraint

EDIT - I fixed it. See answer.
I'm trying to learn how to create constraints programmatically. I tried to constrain a UILabel to the top of the screen, using this code, in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
//the variable label already exists. I make a UILabel.
label = UILabel()
label.text = "Hello"
label.backgroundColor = UIColor(red: 1, green: 1, blue: 0, alpha: 1)
//I add the label to the ViewController
view.addSubview(label)
//I use an array of constraints because I will make more later.
let constraints : [NSLayoutConstraint] = [
NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0.0)
]
view.addConstraints(constraints)
}
When I run my app, I get an error beginning with:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
My intended equation for the constraint was:
label.top = 1.0 * topLayoutGuide.bottom + 0.0
When I created what seems to be an identical constraint with Interface Builder, it worked. However, it doesn't when I try to do it programatically. What's wrong with what I'm doing, and how can I programmatically make a constraint using NSLayoutConstraint that will pin my Label to the top of the screen right below the status bar?
My sources:
Apple - Anatomy of a Constraint
Apple - Programmatically creating Constraints
StackOverflow - SWIFT | Adding constraints programmatically
This can be fixed by setting translatesAutoresizingMaskIntoConstraints to be false, to avoid clashes with existing Mask Constraints:
label.translatesAutoresizingMaskIntoConstraints = false

How to create an editable UITextField programmatically [duplicate]

This question already has an answer here:
A UIButton in my subview won't work
(1 answer)
Closed 6 years ago.
I'm using Xcode 7.3.1 to write an app in Swift to run on iOS 9.3. I'm creating UITextFields programmatically, but can't get them to accept focus when I click in one of them. Here's how I create each UITextField:
self.hoursField = UITextField()
self.hoursField!.borderStyle = UITextBorderStyle.Bezel
self.hoursField!.canBecomeFirstResponder()
self.hoursField!.canBecomeFocused()
self.hoursField!.delegate = self
self.hoursField!.enablesReturnKeyAutomatically = true
self.hoursField!.translatesAutoresizingMaskIntoConstraints = false
self.hoursField!.userInteractionEnabled = true
The UITextFields appear exactly where I want them and look exactly as I want them to look, I just can't get them to accept focus so I can edit the text in the field.
I am implementing all the methods in the UITextFieldDelegate protocol, and returning true from all those methods that return a boolean.
Can anyone explain what I'm doing wrong, or not doing?
Each text field is being created and added to a custom subclass of UIView that contains a couple of UILabels in addition to the UITextField. Here's the relevant part of the custom view's init method that creates the text field:
init(frame: CGRect, charge: (code:String, note:String?, hours:Int)) {
super.init(frame: frame)
// Create the hours UITextField
self.hoursField = UITextField()
self.hoursField!.borderStyle = UITextBorderStyle.Bezel
self.hoursField!.canBecomeFirstResponder()
self.hoursField!.canBecomeFocused()
self.hoursField!.contentVerticalAlignment = UIControlContentVerticalAlignment.Center
self.hoursField!.delegate = self
self.hoursField!.enablesReturnKeyAutomatically = true
self.hoursField!.font = UIFont.systemFontOfSize(14)
self.hoursField!.keyboardType = UIKeyboardType.NumberPad
self.hoursField!.returnKeyType = UIReturnKeyType.Done
self.hoursField!.text = String(charge.hours)
self.hoursField!.textAlignment = NSTextAlignment.Right
self.hoursField!.translatesAutoresizingMaskIntoConstraints = false
self.hoursField!.userInteractionEnabled = true
// Add it to the view
self.addSubview(self.hoursField!)
// Create its layout constraints
let hoursTopMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.codeLabel!, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
let hoursLeftMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.codeLabel!, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: self.hourFieldIndent)
let hoursRightMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.hoursField!, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: self.hourFieldWidth)
// Add the layout constraints to the view
self.addConstraints([hoursTopMarginConstraint, hoursLeftMarginConstraint, hoursRightMarginConstraint])
A stripped-down version of my app that shows the problem I'm having can be downloded from https://github.com/ThomBrando/TextFieldDemo.git
Have you tried
self.hoursField!.becomeFirstResponder()
When I implement a texfield programmatically all that I have to do is instantiate the texfield. let field = UITextFeild() set its delegate let field.delegate = self
extension UIViewController: UITextFieldDelegate {
public func textFieldShouldReturn(textField: UITextField) -> Bool {
self.view.endEditing(true)
return false
}
}
I have never used the two lines you had in your attempt.
.canBecomeFirstResponder
.conBecomeFocused
.enablesReturnKeyAutomatically
I would suggest removing those.
Then if you are trying to get the keyboard to dismiss when anywhere else is tapped on the screen I would suggest adding this wrapped up in an extension so you can add it anywhere.
extension OnboardingPage {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
Then just call hideKeyboardWhenTappedAround() in your viewDidLoad that you want that action on.
Hope that helps. :)
Can you give us a little more info? Like what's the frame of your UITextfield? Because i copied your code and i have the same issue but it was fine for me when i added a frame for UITextfield. Also just want to make sure you're adding the textfield as a subview to it's parent view.
Also I would recommend trying on a device if you have one, the simulator sometimes bugs out and things don't work as expected
I think this happens because you are testing it on the simulator with the keyboard disabled. The simulator will then use your computer's keyboard and won't show the iOS keyboard.
Could you try pressing Cmd-K when you select the UITextField?
The problem turns out to be this line:
let frame:CGRect = CGRect(x: 0, y: totalContentHeight, width: 0, height: 0)
let chargeView:CustomView = CustomView(frame: frame, charge: charge)
So your CustomView has zero width, zero height — zero size. Thus, none of its subviews is touchable. The text fields are its subviews. Thus, they are not touchable. They are visible, because the CustomView does not clip to its bounds; but they are outside their superview, so they are not touchable.
To prove this, just change the first line to this:
let frame:CGRect = CGRect(x: 0, y: totalContentHeight, width: 500, height: 100)
Those are not the "right" numbers, but that doesn't matter. The point is, you will find you can now tap on the text field and type in it! So that should give you enough of a clue to get started.

Swift make percentage of width programmatically

I am programmatically creating multiple buttons in Swift for a UITableViewCell, and would like to set their widths to be a certain percentage width of the UITableViewCell so that they use up all of the space. Right now I am creating the buttons in a for loop (I want to be able to create a variable amount of buttons) and am using
button.buttonWidth = self.contentView.frame.width / numberOfButtons
where button is a UIButton and self is a UITableViewCell and numberOfButtons is obviously the number of buttons. I have also tried:
button.buttonWidth = self.frame.size.width / numberOfButtons
and every combination of using/not using .size and using/not using contentView. These don't seem to work, however, as the buttons are too big. Anyone know how to accomplish this?
NOTE: I CAN'T use Autolayout in the storyboard, as I'm creating a variable number of buttons.
You say:
NOTE: I CAN'T use Autolayout in the storyboard, as I'm creating a variable number of buttons.
You can't add the variable number of buttons right in Interface Builder, but there's nothing to stop you from using autolayout. The basic autolayout constraints you need to set up are:
Set buttons to have same width;
Set buttons to have their leading constraint connected to the previous button's trailing constraint;
The first button should have its leading constraint connected to the container view; and
The last button should have its trailing constraint connected to the container, too.
So, for example, in Swift 3, you could do something like:
var previousButton: UIButton?
for _ in 0 ..< count {
let button = ...
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
// if no "previous" button, hook leading constraint to its superview;
// otherwise hook leading constraint and width to previous button
if previousButton == nil {
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 5).isActive = true
} else {
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: previousButton!.trailingAnchor, constant: 5),
button.widthAnchor.constraint(equalTo: previousButton!.widthAnchor, constant: 5)
])
}
// add vertical constraints
NSLayoutConstraint.activate([
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
view.bottomAnchor.constraint(equalTo: button.bottomAnchor, constant: 5)
])
previousButton = button
}
// make sure to hook last button's trailing anchor to superview, too
view.trailingAnchor.constraint(equalTo: previousButton!.trailingAnchor, constant: 5).isActive = true
Or, in Swift 2:
var previousButton: UIButton?
for _ in 0 ..< buttonCount {
let button = ...
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
// if no "previous" button, hook leading constraint to its superview;
// otherwise hook leading constraint and width to previous button
if previousButton == nil {
button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 5).active = true
} else {
NSLayoutConstraint.activateConstraints([
button.leadingAnchor.constraintEqualToAnchor(previousButton!.trailingAnchor, constant: 5),
button.widthAnchor.constraintEqualToAnchor(previousButton!.widthAnchor, constant: 5)
])
}
// add vertical constraints
NSLayoutConstraint.activateConstraints([
button.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 5),
view.bottomAnchor.constraintEqualToAnchor(button.bottomAnchor, constant: 5)
])
previousButton = button
}
// make sure to hook last button's trailing anchor to superview, too
view.trailingAnchor.constraintEqualToAnchor(previousButton!.trailingAnchor, constant: 5).active = true
And, instead of fixed spacing between the buttons, you also could use UILayoutGuide, too (making the guides the same size as each other but a percent of the width of the buttons, achieving a more natural spacing regardless of the width of the superview).
You also can use UIStackView, which is another great way to get controls evenly spaced in a view.
Bottom line, there are many ways to achieve this using autolayout.
Maybe because you're doing this before the cell layouts its subviews and it's size is the same as size set in Any-Any size class 600 * 600.
Try doing adding your buttons in layoutSubviews method, like this:
override func layoutSubviews() {
super.layoutSubviews()
self.layoutIfNeeded()
// add your buttons here
}
Hi there is multiple ways of solving your problem:
Using Autolayout, you can using Autolayout in code and in StoryBoard ;-). Bear with me, I didn't check the code of Autolayout but the logic is here…
let button1: UIButton!, button2: UIButton!, button3: UIButton!
convenience init() {
self.init(frame:CGRectZero)
contentView.addSubview(button1)
contentView.addSubview(button2)
contentView.addSubview(button3)
button1.translatesAutoresizingMaskIntoConstraints = false
button2.translatesAutoresizingMaskIntoConstraints = false
button3.translatesAutoresizingMaskIntoConstraints = false
addConstraint(
item: button1,
attribute: .Left,
relatedBy: .Equal,
toItem: self,
attribute: .Left,
multiplier: 1,
constant: 8)
)
addConstraint(
item: button2,
attribute: .Left,
relatedBy: .Equal,
toItem: button1,
attribute: .Right,
multiplier: 1,
constant: 8)
)
addConstraint(
item: button1,
attribute: .Left,
relatedBy: .Equal,
toItem: button2,
attribute: .Right,
multiplier: 1,
constant: 8)
}
Source: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/
Using Snapkit
let button1: UIButton!, button2: UIButton!, button3: UIButton!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
button1 = UIButton()
button1.setTitle("Button 1", forState: .Normal)
contentView.addSubview(button1)
button1.snp_makeConstraints { (make) in
make.bottom.equalTo(contentView.snp_botton)
make.left.equalTo(contentView.snp_left)
make.height.equalTo(20)
}
button2 = UIButton()
button2.setTitle("Button 2", forState: .Normal)
contentView.addSubview(button2)
button2.snp_makeConstraints { (make) in
make.bottom.equalTo(contentView.snp_botton)
make.left.equalTo(button2.snp_right)
make.height.equalTo(20)
}
button1 = UIButton()
button1.setTitle("Button 3", forState: .Normal)
contentView.addSubview(button2)
button1.snp_makeConstraints { (make) in
make.bottom.equalTo(contentView.snp_botton)
make.left.equalTo(button2.snp_right)
make.height.equalTo(20)
}
}
Source: http://snapkit.io/docs/
My preferred version is Snapkit because it is less verbose than Autolayout.
UIStackView is exactly what you need. Check out my tutorial below:
http://www.raizlabs.com/dev/2016/04/uistackview/

Resources