I'm trying to programmatically set up constraints for a class in my app so that when the keyboard appears everything moves up. i already got it working before in the storyboard, but when i take what it says in the size inspector and write it in code format, it doesn't work as expected.
...
//memo area
var memoArea = UITextView(frame: CGRectMake(20, 291, 275, 225))
memoArea.backgroundColor = majorColor
memoArea.delegate = self
self.view.addSubview(memoArea)
var memoLine = customShadow(theself: self.view, frame: memoArea.frame)
//Spacer View
var spacer:UIView = UIView(frame: CGRectMake(84, 518, 160, 6))
spacer.alpha = 0
self.view.addSubview(spacer)
//Constraints
var memoAreaToSpacer:NSLayoutConstraint = NSLayoutConstraint(item: spacer, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: memoArea, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 8)
spacerToBottom = NSLayoutConstraint(item: bottomLayoutGuide, attribute: NSLayoutAttribute.Top, relatedBy: .Equal, toItem: spacer, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
view.addConstraint(memoAreaToSpacer)
view.addConstraint(spacerToBottom)
...
so when a keyboard fires a notification this happens.
func updateBottomLayoutConstraintWithNotification(notification: NSNotification) {
let userInfo = notification.userInfo!
let animationDuration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as NSNumber).doubleValue
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let convertedKeyboardEndFrame = view.convertRect(keyboardEndFrame, fromView: view.window)
let rawAnimationCurve = (notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as NSNumber).unsignedIntValue << 16
let animationCurve = UIViewAnimationOptions.init(UInt(rawAnimationCurve))
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
spacerToBottom.constant = CGRectGetMaxY(view.bounds) - CGRectGetMinY(convertedKeyboardEndFrame) - height! - 5
UIView.animateWithDuration(animationDuration, delay: 0.0, options: .BeginFromCurrentState | animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
Here's the full app https://github.com/stanchiang/phoneHub
The code i'm talking about is in the BaseDetailViewController.swift in viewDidLoad().
The constraints i'm transferring from is the Edit Controller in the Main.storyboard file.
also, for reference i got it working through storyboard through this blog post.
http://effortlesscode.com/auto-layout-keyboard-shown-hidden/
thanks for your answers, still trying to get the hang of autolayout so general tips are welcome too.
I'm no Auto Layout expert, but I'll share what I know:
You need to turn off the AutoresizingMask for the views that you are adding constraints to:
view.setTranslatesAutoresizingMaskIntoConstraints(false)
spacer.setTranslatesAutoresizingMaskIntoConstraints(false)
memoArea.setTranslatesAutoresizingMaskIntoConstraints(false)
You are going to need a lot more constraints. I only see 2 in your code. You will need to fully constrain your layout. This includes the settings you are specifying right now with frames (width, height). Positions should probably be specified relative to other objects if you want everything to move when the keyboard shows up. When you got this working in the Storyboard, there had to be more than just 2 constraints for the entire ViewController. You'll need to bring over all of the constraints.
I suggest you add the following 6 constraints to replace the values you were setting with the frames. Put these with your other constraints. I've added the view frames in comments so that you can see where I got the values:
//var memoArea = UITextView(frame: CGRectMake(20, 291, 275, 225))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 275.0))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 225.0))
self.view.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 20.0))
// var spacer:UIView = UIView(frame: CGRectMake(84, 518, 160, 6))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 160.0))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 6.0))
self.view.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 84.0))
Related
On a MacOS Project.
I'm trying to draw a line under a text field (so that by making the text field transparent I can create a "please fill in the blank" style of view that asks the user to input the data). I created a simple subclass of NSView called lineDrawer just to draw the line, then I tried to add an instance of a lineDrawer as a subview to the view where I let the user enter his name. The line should be drawn right below the transparent text field. The text field is an IBOutlet of the view and already in correct position, and I want the line to have the exact same frame as the text field, so that if I draw a path from the lineDrawer's (minX, minY)to (maxX, minY), the path is exactly below the transparent text field.
I can't make it work because after I set
lineView.translatesAutoresizingMaskIntoConstraints = false
and added my own constraints, the custom view wouldn't draw. I think it has something to do with me not giving the program enough information about where exactly to draw the view, but I can't figure out what's lacking here. BTW, if I don't set the constraints, it would just draw the view at its frame's place. If I set the constraints, nothing shows.
The NickName: NSViewController
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
greetingLine.font = .labelFont(ofSize: 50)
nameField.font = .labelFont(ofSize: 50)
nameField.isBezeled = false
let lineView = lineDrawer.init()
lineView.setFrameSize(nameField.frame.size)
self.view.addSubview(lineView)
lineView.translatesAutoresizingMaskIntoConstraints = false
let lineConstraintX = NSLayoutConstraint.init(item: lineView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
let lineConstraintY = NSLayoutConstraint.init(item: lineView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.3, constant: 0)
self.view.addConstraints([lineConstraintY, lineConstraintX])
}
#IBOutlet weak var nameField: NSTextField!
#IBOutlet weak var greetingLine: NSTextField!
and the lineDrawer: NSView:
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
let context = NSGraphicsContext.current?.cgContext
context!.beginPath()
context!.move(to: CGPoint(x: self.bounds.minX, y: self.bounds.minY))
let endPoint = CGPoint.init(x: self.bounds.maxX, y: self.bounds.minY)
context!.addLine(to: endPoint)
context!.move(to: CGPoint(x: 0, y: 0))
context!.addLine(to: CGPoint(x: 10, y: 10))
let loadedColor = ColorGetter.getCurrentThemeColor()
context!.setStrokeColor(loadedColor.cgColor)
context!.setLineWidth(5)
context!.strokePath()
print("drawing")
}
Thanks very much in advance to anyone who would offer help on this!
Can you, please, add behind your layout constraint the variable isActive and set it to be true. Then ist should work.
So, the code you have:
let lineConstraintX = NSLayoutConstraint.init(item: lineView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
let lineConstraintY = NSLayoutConstraint.init(item: lineView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.3, constant: 0)
Must be changed in:
NSLayoutConstraint.init(item: lineView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint.init(item: lineView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.3, constant: 0).isActive = true
Then the auto layout constraint is activated and works. Currently, you did set the auto layout constraint, but you didn't activate it.
Answering my own question:
I added 2 constraints to the custom view's width and height by
NSLayoutConstraint.init(item: lineView, attribute: .width, relatedBy: .equal, toItem: self.nameField, attribute: .width, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint.init(item: lineView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
I think by making
lineView.translatesAutoresizingMaskIntoConstraints = false
I was removing the program's knowledge about both the custom view's position and size, and so I had to re-specify them both manually?
Weird that the program doesn't imply what the size of the custom view should be from its frame's size, which was set.
how do i make two button "Like" and "Comment" with equal space on the left of "Like", equal space in the middle of "Like" and "Comment", and equal space on the right of "Comment" depending on the phone size
here is an image example:
Try the following code it is a bit different approach but hope it will work for you. It is better to set these constraints directly in Storyboard.
let deviceWidth = UIScreen.mainScreen().bounds.size.width
let buttonWidth = 75
let equalPadding = (deviceWidth - (2 * buttonWidth))/3
let centerXOfLikeButton = -(buttonWidth/2 + equalPadding/2)
let centerXOfCommentButton = (buttonWidth/2 + equalPadding/2)
// Like button constraints
let likeBtnTopConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.Top, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20)
let likeBtnWidthConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.Width, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: buttonWidth)
let likeBtnHeightConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 40)
let likeBtnXConstraint = NSLayoutConstraint(item: button, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1, constant: centerXOfLikeButton)
self.view.addConstraint(likeBtnTopConstraint)
self.view.addConstraint(likeBtnWidthConstraint)
self.view.addConstraint(likeBtnHeightConstraint)
self.view.addConstraint(likeBtnXConstraint)
// Comment button constraints
let commentBtnTopConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.Top, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20)
let commentBtnWidthConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.Width, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: buttonWidth)
let commentBtnHeightConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 40)
let commentBtnXConstraint = NSLayoutConstraint(item: button, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1, constant: centerXOfCommentButton)
self.view.addConstraint(commentBtnTopConstraint)
self.view.addConstraint(commentBtnWidthConstraint)
self.view.addConstraint(commentBtnHeightConstraint)
self.view.addConstraint(commentBtnXConstraint)
Using storyboard, please refer screenshots provided below
Crate Two button with equal width and equal height and give "titleEdgeInsets" on each button as bellow,
let insert = likeButton.frame.width / 4
commentButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: insert)
likeButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: insert, bottom: 0, right: 0)
I want to do a simple animation where the logo moves from point A on the screen at size 1 to point B on the screen at size 2
I found
self.logoOutlet.frame = CGRect(x: x, y: y, width: 115, height: 115)
how do i get it so the x is centered no matter what size the phone is?
You either have to do math, to calculate the value of x based on the bounds of the superview of logoOutlet or use AutoLayout and just use a center X constraint (which is probably the better choice).
You can read about the specifics in the documentation: Auto Layout Guide, or use one of the many tutorials on Auto Layout you can find via Google search.
Through a combination of two different answers I found a solution that works
self.logoOutlet.frame = CGRect(x: self.view.frame.midX, y: 26, width: 115, height: 115)
self.logoOutlet.center.x = self.view.center.x
Here's an example using Auto Layout:
#IBAction func animateLogo(sender: UIButton) {
let newView = UIView()
newView.backgroundColor = .redColor()
newView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(newView)
let topConstraint = NSLayoutConstraint(item: newView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: 500.0)
let centerXConstraint = NSLayoutConstraint(item: newView, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1.0, constant: 0.0)
let widthConstraint = NSLayoutConstraint(item: newView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 200)
let heightConstraint = NSLayoutConstraint(item: newView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 200)
NSLayoutConstraint.activateConstraints([topConstraint, centerXConstraint, widthConstraint, heightConstraint])
self.view.layoutIfNeeded()
// Now change the constraint for the second point
widthConstraint.constant = 50
heightConstraint.constant = 50
topConstraint.constant = 50
// Animate the move over 5 seconds
UIView.animateWithDuration(5.0) {
self.view.layoutIfNeeded()
}
}
What is the best practice to resize superview with autolayout if we have inner NSView columns with dynamic heights?
For example. If we have two column layout, where left column height is bigger than right column, the superview height should be as right column height. Than, if we change right column height to be bigger than left column height, superview height should change to height of right column. How to accomplish this?
I made sample project to test this:
Initially we have layout with two columns, where .Bottom constraint of left NSView is attached to bottom of superview.
If we press Make Right Bigger button, I make height of right NSView bigger than left one.
So I want here superview to change height depending on bigger column (right column). Is there a good practice to do so?
Code:
import Cocoa
class ViewController: NSViewController {
let leftView = NSView()
let rightView = NSView()
let button = NSButton()
var rightViewHeightConstraint: NSLayoutConstraint?
override func loadView() {
self.view = TestView()
}
override func viewDidLoad() {
super.viewDidLoad()
leftView.backgroundColor = NSColor.redColor()
rightView.backgroundColor = NSColor.orangeColor()
layoutLeft(view, insertView: leftView)
layoutRight(view, insertView: rightView)
button.title = "Make Right Bigger"
button.target = self
button.action = "makeBigger:"
ViewControllerLayout.layoutBotton(view, insertView: button, bottom: -20)
}
func makeBigger(sender: AnyObject) {
rightViewHeightConstraint?.animator().constant = 150.0
}
func layoutLeft(containerView: NSView, insertView: NSView) {
insertView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(insertView)
let c1 = NSLayoutConstraint(item: insertView, attribute: .Left, relatedBy: .Equal, toItem: containerView, attribute: .Left, multiplier: 1.0, constant: 0.0)
let c2 = NSLayoutConstraint(item: insertView, attribute: .Width, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 0.5, constant: 0.0)
let c3 = NSLayoutConstraint(item: insertView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100.0)
let c4 = NSLayoutConstraint(item: insertView, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1.0, constant: 0.0)
let c5 = NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: -60.0)
containerView.addConstraint(c1)
containerView.addConstraint(c2)
containerView.addConstraint(c3)
containerView.addConstraint(c4)
containerView.addConstraint(c5)
}
func layoutRight(containerView: NSView, insertView: NSView) {
insertView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(insertView)
let c1 = NSLayoutConstraint(item: insertView, attribute: .Right, relatedBy: .Equal, toItem: containerView, attribute: .Right, multiplier: 1.0, constant: 0.0)
let c2 = NSLayoutConstraint(item: insertView, attribute: .Width, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 0.5, constant: 0.0)
let c3 = NSLayoutConstraint(item: insertView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50.0)
let c4 = NSLayoutConstraint(item: insertView, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1.0, constant: 0.0)
let c5 = NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: -60.0)
containerView.addConstraint(c1)
containerView.addConstraint(c2)
containerView.addConstraint(c3)
containerView.addConstraint(c4)
// containerView.addConstraint(c5) // Cant add .Bottom constraint here, because of different column sizes.
rightViewHeightConstraint = c3
}
}
struct ViewControllerLayout {
static func layoutBotton(containerView: NSView, insertView: NSView, bottom: Double) {
insertView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(insertView)
containerView.addConstraint(NSLayoutConstraint(item: insertView, attribute: .CenterX, relatedBy: .Equal, toItem: containerView, attribute: .CenterX, multiplier: 1.0, constant: 0.0))
containerView.addConstraint(NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: CGFloat(bottom)))
}
}
Download test project: GitHub
Managed to accomplish this with constraints only. Just added the container view for columns and set its height as GreaterThanOrEqual to left column and right column.
view.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: leftView, attribute: .Height, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: rightView, attribute: .Height, multiplier: 1, constant: 0))
First create a constraint on superview with height
let heightConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: greaterHeightAmongTwoColumns)
As discussed in the comment, you can post a notification and then let the super view know the updated frame. Super view would check if the updated height of any of the two columns is greater than its current height and update its heightConstraint declared above and then call
[self layoutIfNeeded]
on the superview's superview so that it is laid out with the new frame.
I have a simple
UIViewController that normally works:
view.backgroundColor
UITextView
UIView (as a spacer between the bottom and the textview)
constraints to pin textview to the view which in turn is pinned to the bottomlayoutguide
tapping textview loads keyboard and the spacer view expands accordingly to avoid the keyboard overlapping my textview
...
//var memoArea = UITextView(frame: CGRectMake(20, 291, 275, 225))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 275.0))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 225.0))
self.view.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 20.0))
// var spacer:UIView = UIView(frame: CGRectMake(84, 518, 160, 6))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 160.0))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 6.0))
self.view.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 84.0))
view.setTranslatesAutoresizingMaskIntoConstraints(false)
spacer.setTranslatesAutoresizingMaskIntoConstraints(false)
memoArea.setTranslatesAutoresizingMaskIntoConstraints(false)
...
...
func updateBottomLayoutConstraintWithNotification(notification: NSNotification) {
let userInfo = notification.userInfo!
let animationDuration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as NSNumber).doubleValue
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let convertedKeyboardEndFrame = view.convertRect(keyboardEndFrame, fromView: view.window)
let rawAnimationCurve = (notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as NSNumber).unsignedIntValue << 16
let animationCurve = UIViewAnimationOptions.init(UInt(rawAnimationCurve))
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
spacerToBottom.constant = CGRectGetMaxY(view.bounds) - CGRectGetMinY(convertedKeyboardEndFrame) - height! - 5
UIView.animateWithDuration(animationDuration, delay: 0.0, options: .BeginFromCurrentState | animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
...
But, after adding either a tab bar or a nav bar to a view that has normally working constraints,
3 things break:
the background no longer renders, yielding a black background
the textview doesn't register taps i.e. keyboard doesn't load
view disregards the bottomlayoutguide. it just shifts my objects up as high as their .Top constraints allow them. the constraints between the textview and uiview are still honored though.
commenting out view.setTranslatesAutoresizingMaskIntoConstraints(false) got rid of all errors.