Autolayouts : How to create collapse/Expand dynamic view swift 3/4 - ios

I have a problem that I can't create a view that can collapse it's contents that may be dynamic height , i know it's easy to collapse it with height constant = 0 , but i can't figure out how to make it expand again as the content of that view is dynamic and sub-itmes may be added later
I want that behavior

Your answer has massively overcomplicated a very simple solution.
You should first create your zeroHeightConstraint as a property.
let zeroHeightConstraint: NSLayoutConstraint = dynamicView.heightAnchor.constraint(equalToConstant: 0)
You could do this as an outlet from the storyboard too if you wanted.
Now add your button action...
#IBAction func toggleCollapisbleView() {
if animationRunning { return }
let shouldCollapse = dynamicView.frame.height != 0
animateView(isCollapse: shouldCollapse,
buttonText: shouldCollapse ? "Expand" : "Collapse")
}
private func animateView(isCollapse: Bool, buttonText: String) {
zeroHeightConstraint.isActive = isCollapse
animationRunning = true
UIView.animate(withDuration duration: 1, animations: {
self.view.layoutIfNeeded()
}, completion: { _ in
self.animationRunning = false
self.button.setTitle(buttonText, for: .normal)
})
}
Always avoid using view.tag in your code. It should always be seen as "code smell". There is almost always a much more elegant way of doing what you are trying to do.
Regarding the bottom constraint. You should really be using a UIStackView to layout the contents of the inside of the view here. By doing that you no longer need to iterate the subviews of your contentView you can access the UIStackView directly. And do the same thing there. In fact... a much better way would be to create the bottomConstraint against the view using a priority less than 1000. e.g. 999.
By doing that you don't have to remove or add that constraint at all. When the zeroHeightConstraint is not active it will be there and work. When the zeroHeightConstraint is active it will override the bottom constraint automatically.

Related

Hide one UIView and show another

When I click on an a segment of my UISegmentedControl, I want one of two UIViews to be displayed. But how do I arrange it, that I only show the new view. Currently I am calling thisview.removeFromSuperview() on the old one, and then setup the new all from scratch. I also tried setting all the HeightConstants of the views subviews to zero and then set the heightConstants of the view itself to zero but I'd rather avoid that constraints-surgery..
What better approaches are there?
Agree with #rmaddy about using UIView's hidden property, a nice simple way to cause a view to not be drawn but still occupy its place in the view hierarchy and constraint system.
You can achieve a simple animation to make it a bit less jarring as follows:
UIView.animate(withDuration:0.4, animations: {
myView.alpha = 0
}) { (result: Bool) in
myView.isHidden = true
}
This will fade the alpha on the view "myView", then upon completion set it to hidden.
The same animation concept can be used also if you've views need to re-arrange themselves, animating layout changes will be a nice touch.
Based on #rmaddy and #CSmiths answer, I built the following function:
func changeView(newView: UIView, oldView: UIView) {
newView.isHidden = false
newView.alpha = 0
UIView.animate(withDuration:0.4, animations: {
oldView.alpha = 0
newView.alpha = 1
}) { (result: Bool) in
oldView.isHidden = true
}
}
I feel dumb now for all the hours I spent on that constraint-surgery. :|

How do I update a constraint from the Storyboard without an IBOutlet or Identifier?

I have a lot of views that are created in the storyboard, but I want them to be able to update their constraints dynamically without having to use an IBOutlet each time.
I started by making a custom class for the superview of the view I want to update, and change its subview's bottom constraint like this:
myView.constraints.filter{ $0.firstAnchor is NSLayoutAttribute.bottom }.constant -= 200
'NSLayoutAttribute.bottom' doesn't seem to be the correct way to check the type of the Anchor.
How do I check the type of the constraints I want to change?
Am I correct in updating the constraints in the superview of the view I want to change, not the view itself?
NSLayoutConstraint from iOS7 have a property called identifier, from code or from IB you can set this property.
After that to get the constraint you are looking for is just a matter of searching it in a particular view.
Consider this UIView extension:
func constraint(withIdentifier:String) -> NSLayoutConstraint? {
return constraints.filter{ $0.identifier == withIdentifier }.first
}
As per dahlia_boy's suggestion, I used UIView.animate to achieve this functionality, however it doesn't seem to be permanent:
translatesAutoresizingMaskIntoConstraints = true
UIView.animate(withDuration: 1, animations: {
self.frame.size.height -= 200
})

How to Update NSLayoutConstraint in StoryBoard?

I created 2 Button in my superView , Now I want change the #IBOutlet weak var bottomLayOut: NSLayoutConstraint! depend on the user role .
the example is: if the user is a agent role but not a teacher role I want update the NSLayoutConstraint 's second item from Teacher Entry .bottom to AgentEntry button's bottom .
is that posible?
Update :
solve this by turn translatesAutoresizingMaskIntoConstraints to true:
e.g : teacherBtn.translatesAutoresizingMaskIntoConstraints = true
than, we can use both constraints and code to change teacherBtn's frame
Two ways I can think of to do this off the top of my head:
Set up the constraints programmatically. Create a property containing the current constraints on the buttons, and then you can do something along the lines of (disclaimer: written in Chrome, may contain typos, edit as appropriate):
` if let constraints = self.buttonConstraints {
NSLayoutConstraint.deactivate(constraints)
}
let views: [String : UIView] = ["Agent" : self.agentButton, "Teacher" : self.teacherButton]
let newConstraints: [NSLayoutConstraint] = {
switch role {
case .teacherAndAgent:
self.teacherButton.isHidden = false
self.agentButton.isHidden = false
return NSLayoutConstraint.constraints(withVisualFormat: "V:[agent]-[teacher]-|", metrics: nil, views: views)
case .teacherOnly:
// you get the idea
case .agentOnly:
// ditto
}
}()
NSLayoutConstraint.activate(newConstraints)
self.buttonConstraints = newConstraints`
What might be simpler, though, is to use a UIStackView to hold your buttons. On current macOS at least, NSStackView automatically adjusts the layout based on which of its views are hidden; if UIStackView on iOS behaves the same way, it may do a lot of the work for you without having to manually fuss with layout constraints.

Cannot change the height of Login Button in FBSDKLoginKit?

I am using FBSDKLoginKit in iOS with Swift.
Up until recently it has been working perfectly, however I now cannot override the height of my button in the Storyboard?
The height of the button is now much smaller for some reason. I have tried setting height constraints for the button, putting the button in a stack view and set to fill proportionally and even override the button height in the SDK with no luck.
If I change the button to a normal UIButton the layout constraints work perfectly.
This is what the button looks like when I run the app.
This is how I would like the button to look - size wise.
I've also run into this problem. The reason for this is explained in the 4.18.0 to 4.19.0 upgrade guide:
The FBSDKLoginButton UI has changed in 4.19.0. Instead of "Log in with Facebook", the button now displays "Continue with Facebook". The button color is changed to #4267B2 from #3B5998. The button height is now fixed at 28 due to use of smaller font size and paddings around a larger Facebook logo.
The only workaround I found so far is to downgrade the SDK version to 4.18.0 (it did the job for me).
It is possible that FB will address this issue (...that they've created for many people) in one of the future updates to the SDK.
Towards a more permanent solution, we can see the specific changes that caused this, on GitHub. The change I find most suspicious starts on line 194:
[self addConstraint:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:28]];
If the above constraint is removed/disabled, it could help reverse the situation. It should look approximately like this (I don't have an IDE at hand at the time of writing):
// Obtain all constraints for the button:
let layoutConstraintsArr = fbLoginButton.constraints
// Iterate over array and test constraints until we find the correct one:
for lc in layoutConstraintsArr { // or attribute is NSLayoutAttributeHeight etc.
if ( lc.constant == 28 ){
// Then disable it...
lc.active = false
break
}
}
When I get a chance to test the above or if I find a better solution, I'll update the answer.
You can conveniently achieve this with a simple override of the facebook button.
Swift:
class FacebookButton: FBSDKLoginButton {
override func updateConstraints() {
// deactivate height constraints added by the facebook sdk (we'll force our own instrinsic height)
for contraint in constraints {
if contraint.firstAttribute == .height, contraint.constant < standardButtonHeight {
// deactivate this constraint
contraint.isActive = false
}
}
super.updateConstraints()
}
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: standardButtonHeight)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let logoSize: CGFloat = 24.0
let centerY = contentRect.midY
let y: CGFloat = centerY - (logoSize / 2.0)
return CGRect(x: y, y: y, width: logoSize, height: logoSize)
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
if isHidden || bounds.isEmpty {
return .zero
}
let imageRect = self.imageRect(forContentRect: contentRect)
let titleX = imageRect.maxX
let titleRect = CGRect(x: titleX, y: 0, width: contentRect.width - titleX - titleX, height: contentRect.height)
return titleRect
}
}
In this code sample standardButtonHeight is a defined constant with the desired button height.
Also note that the logo size of 24.0 is the same size used in version 4.18 of the SDK.
As for now the Facebook button has only one constraint which is the height constraint and you can just remove all constraints of the button and add yours.
facebookSignInButton.removeConstraints(facebookSignInButton.constraints)
But of course this can change in the future and you might remove a constraint that you don't want to. Maybe a better solution would be if you remove only that problematic constraint.
if let facebookButtonHeightConstraint = facebookSignInButton.constraints.first(where: { $0.firstAttribute == .height }) {
facebookSignInButton.removeConstraint(facebookButtonHeightConstraint)
}
So I took #Dev-iL's solution and tweaked it to something a bit more future proof. I'm very new to this so it took me a few hours to figure it out, but I thought I'd share since it specifically deactivates the height constraint based on being a height constraint instead of based on the constant value.
I've used a subview classed as the Facebook button in my storyboard and have set the new constraint there.
I prefer this method and feel its a cleaner approach.
Note: I believe for a height constraint it will always be the first value however please correct me if I'm wrong and I'll update with an edit. As I mentioned I'm new to this
Edit: I decided to include the constant value of 28 to allow for my storyboard height constraint to be skipped during the removal. This isn't needed if you add the constraint programmatically after the removal
for const in fbLoginButton.constraints{
if const.firstAttribute == NSLayoutAttribute.height && const.constant == 28{
fbLoginButton.removeConstraint(const)
}
}
This little autoclosure in Swift 4.0 also works perfectly if you have no reason to remove the constraint, just override it.
let facebookLoginButton = FBSDKLoginButton()
if let constraint = facebookLoginButton.constraints.first(where: { (constraint) -> Bool in
return constraint.firstAttribute == .height
}) {
constraint.constant = 40.0
}
Or if you hate let statements:
let facebookLoginButton = FBSDKLoginButton()
facebookLoginButton.constraints.first(where: { (constraint) -> Bool in
return constraint.firstAttribute == .height
})?.constant = 40.0
If you are after just changing the height of your button, you can simply adjust the constant of the already present height constraint on the button, after adding the button in your Storyboard:
for constraint in facebookButton.constraints where constraint.firstAttribute == .height {
constraint.constant = YOUR_Height
}
This code can be placed in viewDidLoad().
I could manage to change the height of the button this way:
I added a view facebookButtonView to the storyboard with the size that i want, and then in the viewDidLoad i simple do this:
let loginButton = LoginButton(frame: self.facebookButtonView.frame, readPermissions: [ .publicProfile ])
self.view.addSubview(loginButton)
The Facebook button take the same size as the facebookButtonView. I tested with height 50 and it's working.
As a last resort, try implementing your own custom button to act as a Facebook Login button. They might be preventing the customization of the button from the SDK.
https://developers.facebook.com/docs/swift/login
-- There is a section here with example code - "Custom Login Button". It doesn't seem complicated.
We had the same problem. We solve this problem by creating the button in code with initWithFrame method.
from documentation
FBSDKLoginButton has a fixed height of #c 30 pixels, but you may change the width. initWithFrame:CGRectZero
will size the button to its minimum frame.
this solution is working for us
let facebookButton = FBSDKLoginButton(frame:facebookButtonPlaceholder.bounds)
facebookButton.readPermissions = ["email"]
facebookButton.backgroundColor = UIColor.clear
facebookButtonPlaceholder.addSubview(facebookButton)
facebookButtonPlaceholder.backgroundColor = UIColor.clear
Realising this is an old question, I've found a solution and am posting it as an answer for future reference.
After you have installed the FBSDKLoginKit via a Pod, look in your directory on the left in XCODE and open your PODS -- not your Podfile. Open FBSDKLoginKit, and open FBSDKLoginButton.m file.
You will now see an alert indicating that you are editing. Ignore the alert, that is take notice of the message and make sure that you don't change anything other than your target info. Change the two fields that dictate Facebook button height in the file as seen below:
Pictures to help you through the guide above:
project structure files to open
first field to change
second field to change
EASIEST SOLUTION, no need to deal with programmatic rects, just do it in storyboard
Autolayout does not work on Facebook button(FBSDKButton) anymore. I changed its height using buttons frame.
let fbLoginbutton = FBSDKLoginButton()
view.addSubview(fbLoginbutton)
fbLoginbutton.frame = CGRect(x: 38, y: 397, width: 300, height: 38)
You can set it according to your requirement. Although I'm still not able to change its font & Logo size.
Xamarin.iOS example using Linq.
I created the button in the storyboard file, and assigned a height there. However, I could not just remove all height constraints because the one I set in the storyboard was getting removed as well. I had to check to see if there is an existing height constraint of size 28 - and remove that one
var contraint = this.FacebookLoginButton?.Constraints?.Where(x => x.FirstAttribute.Equals(NSLayoutAttribute.Height) && x.Constant == 28f)?.FirstOrDefault() ?? null;
if (contraint != null)
{
this.FacebookLoginButton.RemoveConstraint(contraint);
}

How can I define animations for views using auto layout without relying on tons of constraint references and boilerplate code?

Before the switch to auto layout, applying animations to a view was easy - you'd just change the frame in UIView.layoutWithDuration:. When constraints come into the picture, things get more complicated. The most common method of animating a view that uses auto layout is to keep a reference to the constraint(s) you want to change, then set the constant value, but this is very difficult to design around, and can affect other views if their constraints depend on the position of the view you want to animate.
There must be a better way to do this. I'd love to be able to do something like view.translateFrom(direction: .left, distance: 30, duration: 0.3, delay: 0).
Ultimately you will want to use constraints to achieve simple animations. The overriding reason for this is -
"Choosing any other way is simply swimming against the stream"
If you don't want to 'litter' your classes with #IBOutlets for the constraints you wish to animate then you can in most cases obtain a reference to a pertinent constraint in code:
Handy extension
import UIKit
extension UIView {
func constraint(attribute: NSLayoutAttribute) -> NSLayoutConstraint? {
var constraint : NSLayoutConstraint? = .none
for potentialCenterXConstraint in self.constraints {
if potentialCenterXConstraint.firstAttribute == attribute {
constraint = potentialCenterXConstraint
break
}
}
return constraint
}
}
client code use
if let centerXConstraint = someView.constraint(attribute: .centerX) {
// Do something funky with centerXConstraint
}

Resources