How to create a slide animation inside a view controller? - ios

I'd like to animate a view to slide in and out from the left.
What I did so far:
When the user clicks on the upper left icon, an action (show/hide menu-view) is triggered.
The "menu-view" includes the dark mask view, the semi-transparent white view and all three views (label + image).
Now this menu view shall slide in and out.
I tried to add a constraint to the menu view:
func viewDidLoad() {
super.viewDidLoad()
menuView.translatesAutoresizingMaskIntoConstraints = false
menuViewLeftConstraint = NSLayoutConstraint(item: menuView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: -1000)
menuViewLeftConstraint.isActive = true
}
and I toggled the constant on every click the user performs (-1000 or 0).
But the animation does not look like I thought it would.

Why do this programmatically with a fixed constant? Set the right of your uiview (trailing) equal to the leading (left) of your superciew (uiviewcontroller). Create an outlet of that constraint and animate it by adding a constant which is equal to the uiview’s width and maybe some offset.
Alternative you can make your subview equal to your superviews width - someoffset, equal height -someoffset, centerX and centerY to the superview and animate the centerX constraint.

Related

SWIFT: programmatically created constraints not updating properly

So I have a UIView near the bottom of the superview, with a textfield inside of it. When the user taps inside the textfield to begin editing, I am bringing up the entire UIView with the keyboard. One problem with this, is that if you have constraints on said UIView, when you start typing in the textfield, the UIView conforms to its constraints and goes back down to its original spot, hidden by the keyboard. I created a work around by overriding updateViewContraints(), removing the default constraint (the one I set in storyboard), when the textfield is being edited, and adding a new constraint to keep it where I want it. Then, when the editing ends, the code is supposed to bring the UIView back down, and remove the new constraint, and replace it with the original. Here's that code:
override func updateViewConstraints() {
// The new constraint, active when the keyboard is shown
let constraintWhenKeyboardShown = NSLayoutConstraint(item: searchRadiusView!, attribute: .bottom, relatedBy: .equal, toItem: super.view, attribute: .bottom, multiplier: 1.0, constant: -310.0)
// The default constraint, active at all other times
let defaultConstraint = NSLayoutConstraint(item: searchRadiusView!, attribute: .bottom, relatedBy: .equal, toItem: super.view, attribute: .bottom, multiplier: 1.0, constant: -30.0)
if radiusTextfield.isEditing {
view.removeConstraint(defaultConstraint)
view.addConstraint(constraintWhenKeyboardShown)
print("new")
} else {
view.removeConstraint(constraintWhenKeyboardShown)
view.addConstraint(defaultConstraint)
print("default")
}
self.view.layoutIfNeeded()
super.view.updateConstraints()
}
I call the updateViewConstraints in each of my textfield methods:
// Raises the searchRadiusView upon editing
func textFieldDidBeginEditing(_ textField: UITextField) {
let newSearchRadiusViewYValue = self.searchRadiusView.center.y - 280
searchRadiusView.center.y = newSearchRadiusViewYValue
print(searchRadiusView.constraints)
self.updateViewConstraints()
}
// Lowers the searchRadiusView upon dismissal
func textFieldDidEndEditing(_ textField: UITextField) {
let newSearchRadiusViewYValue = self.searchRadiusView.center.y + 280
searchRadiusView.center.y = newSearchRadiusViewYValue
print("ended")
print(searchRadiusView.constraints)
self.updateViewConstraints()
}
However, upon ending editing, my UIView is not moving back down to its original position, and the debugger says
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x607000071590 UIView:0x61300002f280.bottom == UIView:0x613000071380.bottom - 310 (active)>",
"<NSLayoutConstraint:0x607000297f80 UIView:0x61300002f280.bottom == UIView:0x613000071380.bottom - 30 (active)>"
)
It seems that the 'constraintWhenKeyboardShown' constraint is still there (the one ending in -310), even though I have 'view.removeConstraint(constraintWhenKeyboardShown)
' in the updateViewConstraints() function. Is that what's causing my problem? Does anyone know how to fix this? Thanks

View on Top of UITabBar

Similar to what the Spotify or Apple Music app does when a song is playing, it places a custom view on top of the UITabBar:
Solutions I've tried:
UITabBarController in a ViewController with a max-sized Container View, and the custom view on top of the Container View49pt above the Bottom Layout Guide:
Problem: Any content in ViewControllers embedded in the UITabBarController constrained to the bottom don't show because they're hidden behind the custom layout. I've tried overriding size forChildContentContainer in UITabBarController, tried updating the bottom layout guide, Nothing. I need to resize the frame of container view of the UITabBarController.
Tried #1 again, but tried solving the problem of content hiding behind it by increasing the size of UITabBar, and then using ImageInset on every TabBarItem to bring it down, and adding my custom view on top of the UITabBar. Hasn't worked really well. There are going to be times when I want to hide my custom view.
UITabBarController as root, with each children being a ViewController with a Container View + my custom view:
But now I have multiple instances of my custom view floating around. If I want to change a label on it, have to change it to all views. Or hide, etc.
Override the UITabBar property of UITabBarController and return my custom UITabBar (inflated it with a xib) that has a UITabBar + my custom view. Problem: Probably the most frustrating attempt of all. If you override that property with an instance of class MyCustomTabBar : UITabBar {}, no tab shows up! And yes, I set the delegate of myCustomTabBar to self.
Leaning towards #3, but looking for a better solution.
This is actually very easy if you subclass UITabBarController and add your view programmatically. Using this technique automatically supports rotation and size changes of the tab bar, regardless of which version you are on.
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
//...do some of your custom setup work
// add a container view above the tabBar
let containerView = UIView()
containerView.backgroundColor = .red
view.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// anchor your view right above the tabBar
containerView.bottomAnchor.constraint(equalTo: tabBar.topAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
}
I got it!
In essence, I increased the size of the original UITabBar to accomodate a custom view (and to shrink the frame of the viewcontrollers above), and then adds a duplicate UITabBar + custom view right on top of it.
Here's the meat of what I had to do. I uploaded a functioning example of it and can be found in this repo:
class TabBarViewController: UITabBarController {
var currentlyPlaying: CurrentlyPlayingView!
static let maxHeight = 100
static let minHeight = 49
static var tabbarHeight = maxHeight
override func viewDidLoad() {
super.viewDidLoad()
currentlyPlaying = CurrentlyPlayingView(copyFrom: tabBar)
currentlyPlaying.tabBar.delegate = self
view.addSubview(currentlyPlaying)
tabBar.isHidden = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
currentlyPlaying.tabBar.items = tabBar.items
currentlyPlaying.tabBar.selectedItem = tabBar.selectedItem
}
func hideCurrentlyPlaying() {
TabBarViewController.tabbarHeight = TabBarViewController.minHeight
UIView.animate(withDuration: 0.5, animations: {
self.currentlyPlaying.hideCustomView()
self.updateSelectedViewControllerLayout()
})
}
func updateSelectedViewControllerLayout() {
tabBar.sizeToFit()
tabBar.sizeToFit()
currentlyPlaying.sizeToFit()
view.setNeedsLayout()
view.layoutIfNeeded()
viewControllers?[self.selectedIndex].view.setNeedsLayout()
viewControllers?[self.selectedIndex].view.layoutIfNeeded()
}
}
extension UITabBar {
open override func sizeThatFits(_ size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = CGFloat(TabBarViewController.tabbarHeight)
return sizeThatFits
}
}
Since iOS 11 this became a little easier. When you add your view, you can do the following:
viewControllers?.forEach {
$0.additionalSafeAreaInsets = UIEdgeInsets(
top: 0,
left: 0,
bottom: yourView.height,
right: 0
)
}
Your idea to put it in a wrapper viewcontroller is good, but it will only cause overhead (more viewcontrollers to load in memory), and issues when you want to change the code later on. If you want the bar to always show on your UITabBarController, then you should add it there.
You should subclass UITabBarController and load the custom bar from a nib. There you will have access to the tabbar (so you can place your bar correctly above it), and you will only load it in once (which solves your problem that you will face having a different bar on each tab).
As for your views not reacting to the size of the custom bar, I don't know how you can do that, but my best suggestion is to use a public variable and notifications that you listen to in your individual tabs.
You can then use that to change the bottom constraint.
Besides playing with UITabBar or container vc, you could also consider adding the view in the App Delegate to the main window like in following post:
View floating above all ViewControllers
Since your view is all around along with the Tab bar, it is totally ok to make it in the App Delegate.
You can always access the Floating view from App Delegate Singleton by making it a property of the App Delegate. It is easy then to control its visibility in anywhere of your code.
Changing constant of the Constraints between the Floating view and super view window can adjust the position of the view, thus handsomely respond to orientation changes.
Another(similar) approach is to make the floating view another window like the uid button.
Unless I've misunderstood, you could create a custom view from your UITabBarController class. You can then insert it above and constrain it to the tabBar object, which is the tabBar associated with the controller.
So from your UITabBarController class, create your custom view
class CustomTabBarController: UITabBarController {
var customView: UIView = {
let bar = UIView()
bar.backgroundColor = .white
bar.translatesAutoresizingMaskIntoConstraints = false
return bar
}()
In viewDidLoad() add your custom view to the UITabBarController's view object and place it above the tabBar object
override func viewDidLoad() {
super.viewDidLoad()
...
self.view.insertSubview(customView, aboveSubview: tabBar)
Then after your custom view is added as a subView, add constraints so it's positioned correctly. This should also be done in viewDidLoad() but only after your view is inserted.
self.view.addConstraints([
NSLayoutConstraint(item: customView, attribute: .leading, relatedBy: .equal, toItem: tabBar, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: customView, attribute: .trailing, relatedBy: .equal, toItem: tabBar, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: customView, attribute: .top, relatedBy: .equal, toItem: tabBar, attribute: .top, multiplier: 1, constant: -50),
NSLayoutConstraint(item: customView, attribute: .bottom, relatedBy: .equal, toItem: tabBar, attribute: .top, multiplier: 1, constant: 0)
])
There's a bunch of creative ways you can setup constraints to do what you want, but the constraints above should attach a view above your tabBar with a height of 50.
Make the view's frame with the height of tab bar and brings it to top, 2. set tabBar hidden is true.

Add constraints between two sub views programmatically in Swift

I have two subviews that I created, one in Storyboard one with code, I want to anchor the second view (created with code) to the first view (in storyboard) with some constraints so that the second view sits below the first view:
class ViewController: UIViewController{
#IBOutlet weak var view1: UIView!
override func viewDidLoad(){
super.viewDidLoad()
setUp()
}
func setUp(){
var view2 = UIView()
view2.setTranslatesAutoresizingMaskIntoConstraints(false)
view2.frame = CGRectMake(10,10,10,10)
self.view.addSubview(view2)
self.view.addConstraint(NSLayoutConstraint(item: view1, attribute: NSLayoutAttribute.TopMargin, relatedBy: NSLayoutRelation.Equal, toItem: view2, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 10))
}
}
The problem is that I got an error saying When added to a view, the constraint's items must be descendants of that view. Is it bad practice to have some views in storyboard and others in code?
You have a few problems.
First, the error means that view1 and view2 are not both part of self.view's view hierarchy. This is probably because view1 hasn't been decoded from the storyboard yet. Try moving this code from viewDidLoad() to awakeFromNib(), which is when they're guaranteed to have been loaded.
Second, you're trying to set the view's frame:
view2.frame = CGRectMake(10,10,10,10)
This frame will get overwritten by the layout engine making it pointless. Delete this line.
If you're going to use auto-layout, you need to unambiguously specify both the size (height & width) and position (x & y) of the view. The constraint you added only specifies the y-origin, so you also need to add more constraints (probably 3 more) to specify the x-origin, the width, and the height, which are currently undefined.
Is it bad practice to have some views in storyboard and others in code?
No, it's common.

button position change when screen rotate in tableviewcell

I have to manage my UIButton position at the right side of a UITableViewCell like an image below.
in this cell I gave all my constraints from storyboard except of button..because I created button at runtime like below
In tableview:cellForRowAtIndexPath method
let mybutton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var width:CGFloat = UIScreen.mainScreen().bounds.width
mybutton.frame = CGRectMake(width - 108, 8, 100, 31)
mybutton.tag = indexPath.row
cell?.contentView.addSubview(mybutton)
So the problem is when we launch the app in portrait its ok but when we rotate it, button displays at portrait position...for e.g. if the button position at portrait 220 then in landscape it displays at 220 and after scrolls it looks ok because of cell reusability...
To solve this I'll trying to manually add few constraints to button.I don't know much about how to add constraints programatically but i'll add constraints like below one for top position..and similar with trailing
cell?.contentView.addConstraint(
NSLayoutConstraint(item:imageview ,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem:mybutton ,
attribute:NSLayoutAttribute.Top,
multiplier: 1, constant: 8))
but it displays breaking constraints...
So my questions are...
How to deal with storyboard + manually constraints
How to position button at the right side of a cell
I normally use the Auto-layout Visual Format
//Horizontal constraints
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[button]", options: nil, metrics: metrics, views: views)
self.view.addConstraints(horizontalConstraints)
//Vertical constraints
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|-20-[button]", options: nil, metrics: metrics, views: views)
self.view.addConstraints(verticalConstraints)
Basically you set V or H for vertical and horizontal fallow by the UI item "|" for the board of the screen, than the -value in pixels- and finally the next [UIComponent]
You can find a nice tutorial here
I have not enough reputation for comment that's why i am answering the question
You can reload Data after orientation changed.
It will solve the problem
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
tableVIew.reloadData()
}

How to make a button half the width of a UITableViewCell using constraints (Storyboard or Programatically)

I have a project I'm working on that needs two buttons with some data that will take up exactly half the width of the UITableViewCell that they are in.
In the past when I have wanted to do this I usually set a constraint that the button will be equal widths to its superview and give it a multiplier of 0.5.
For some reason however inside the UITableViewCell I can't get the Storyboards to give me this option. The "Equal Widths" constraint in the GUI is grayed out.
I resolved to just do it programmatically so in the custom cell I tied the following code. I've tried putting the cellInit() method below being called in the awakeFromNib and that gave an error. I've tried also just calling it on cellForRowAtIndexPath when the cell is loaded, and got the same error.
import UIKit
class PollCell: UITableViewCell {
#IBOutlet weak var option1: UIButton!
#IBOutlet weak var option2: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
//cellInit() //Commented out because causes error
}
func cellInit(){
option1.addConstraint(NSLayoutConstraint(item: option1, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 0.5, constant: 0))
}
}
This is the error that I am getting:
'NSInternalInconsistencyException', reason: 'Impossible to set up layout with view hierarchy unprepared for constraint.
What I'm trying to achieve is pretty standard, so I assume this isn't anything to crazy and I'm probably doing something the wrong way. Either way I assume plenty of newcomers like myself will run into this. Thanks in advance!
In the comments, we discussed that you leverage contentView.frame.maxX
Alternatively, you can use AutoLayout: Make sure you setTranslatesAutoresizingMaskIntoConstraints(false)
Assign tags (optional). You only havetwo buttons but for more than two, I would use tags so you don't need to manually type UIButton for every button.
addConstraint(NSLayoutConstraint(item: self.viewWithTag(1) as UIButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 0.33, constant: 0))
addConstraint(NSLayoutConstraint(item: self.viewWithTag(2) as UIButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 0.66, constant: 0))
OR VFL using a Dictionary:
for button in buttonsDictionary.keys {
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[\(button1)]-[\(button2)]|", options: .allZeros, metrics: nil, views: buttonsDictionary))
}
call cell.updateConstraints() in your cellForRowAtIndexPath in TableView.
You can learn more in the link below: They have an example of two side by side buttons:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutinCode/AutoLayoutinCode.html#//apple_ref/doc/uid/TP40010853-CH11-SW1
I've encountered a similar problem like yours before. What I did was put a UIView in the cell first with its top, left, right, bottom constraints set to all 0, then place the button on top of the view. This way I get the 'equal width' option.

Resources