can an NSLayoutConstraint be re-activated? - ios

I have a result container that a user can expend and contract. I'd like to remove a constraint and add a new one. Clicking on it works fine but clicking a second time (ie setting newConstraint.active=false and resultTopConstraint=true causes it to crash). I have the following:
#IBAction func toggleResultContainer(sender: AnyObject) {
isResultsOpen = !isResultsOpen
//resultTopConstraint.constant = isResultsOpen ? -300.0 : 0.0
self.view.sendSubviewToBack(searchView)
let newConstraint = NSLayoutConstraint(
item: resultsContainer,
attribute: .Top,
relatedBy: .Equal,
toItem: resultsContainer.superview!,
attribute: .Top,
multiplier: 1.0,
constant: 30.0
)
if(isResultsOpen){
resultTopConstraint.active = false
newConstraint.active = true
}else{
resultTopConstraint.active = true
newConstraint.active = false
}
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 10.0, options: .CurveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
and get the Unable to simultaneously satisfy constraints.
Should the above code work and this is really a simultaneously satisfy constraints issue? I have tried setting the constraint
#IBOutlet var resultTopConstraint: NSLayoutConstraint!
to both weak and strong (per https://stackoverflow.com/a/28717185/152825) but doesn't seem to have an effect. A

Try this:
if (isResultsOpen) {
NSLayoutConstraint.deactivateConstraints([resultTopConstraint])
NSLayoutConstraint.activateConstraints([newConstraint])
} else {
NSLayoutConstraint.deactivateConstraints([newConstraint])
NSLayoutConstraint.activateConstraints([resultTopConstraint])
}

Related

How do I add constraints to a subview loaded from a nib file?

I'm trying to load a sub view on to every single page of my app from a nib file. Right now I'm using a somewhat unusual approach to loading this sub view in that I am doing it through an extension of UIStoryboard (probably not relevant to my problem, but I'm not sure). So this is how the code looks when I load the nib file:
extension UIStoryboard {
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
viewController.view.addSubview(myCustomSubview)
}
}
}
This code does what it's supposed to do and adds "MyCustomSubview" to the view controller (I won't go in to detail on exactly how this method gets called because it works so it doesn't seem important). The problem is I can't for the life of me figure out how to add constraints that effect the size of myCustomSubview. I have tried putting code in the function I showed above as well as in the MyCustomSubview swift file to add constraints but no matter what I do the subview never changes.
Ideally the constraints would pin "MyCustomSubview" to the bottom of the ViewController, with width set to the size of the screen and a hard coded height constraint.
Here are the two main methods I tried (with about 100 minor variations for each) that did NOT work:
Method 1 - Add constraint directly from "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let top = NSLayoutConstraint(item: myCustomSubview, attribute: .top, relatedBy: .equal
, toItem: viewController.view, attribute: .top, multiplier: 1, constant: 50.0)
viewController.view.addSubview(myCustomSubview)
viewController.view.addConstraint(top)
}
}
Method 2 - Add constraint outlets and setter method in MyCustomSubview
class MyCustomSubview: UIView {
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
func setConstraints(){
self.widthConstraint.constant = UIScreen.main.bounds.size.width
self.heightConstraint.constant = 20
}
}
And call setter method in "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
myCustomSubview.setConstraints()
viewController.view.addSubview(myCustomSubview)
}
}
(*note: the actual constraints of these examples are irrelevant and I wasn't trying to meet the specs I mentioned above, I was just trying to make any sort of change to the view to know that the constraints were updating. They weren't.)
Edit : Changed "MyCustomNib" to "MyCustomSubview" for clarity.
When you add constraints onto a view from a Nib, you have to call yourView.translatesAutoresizingMaskIntoConstraints = false, and you also need to make sure that you have all 4 (unless it's a label or a few other view types which only need 2) constraints in place:
Here's some sample code that makes a view fill it's parent view:
parentView.addSubview(yourView)
yourView.translatesAutoresizingMaskIntoConstraints = false
yourView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor).isActive = true
Edit: I've actually come around to perferring this method of adding NSLayoutConstraints, even though the results are the same
NSLayoutConstraint.activate([
yourView.topAnchor.constraint(equalTo: parentView.topAnchor),
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
])
For anyone who comes across this in the future, this is the solution I came up with by tweaking this answer a little bit
Add a setConstraints(withRelationshipTo) method in the swift class that corresponds to the nib file:
class MyCustomSubview: UIView {
func setConstraints(withRelationshipTo view: UIView){
self.translatesAutoresizingMaskIntoConstraints = false
// Replace with your own custom constraints
self.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Then call the setConstraints method after you add the nib file to the view (probably in viewWillAppear or viewDidLoad of a view controller )
class MyViewController: UIViewController {
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let view = self.view // Added for clarity
view.addSubview(myCustomSubview)
myCustomSubview.setConstraints(withRelationshipTo: view)
}
}
}
You can use this extension for anywhere you're going to add a subview to a existing UIView.
extension UIView {
func setConstraintsFor(contentView: UIView, left: Bool = true, top: Bool = true, right: Bool = true, bottom: Bool = true) {
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
var constraints : [NSLayoutConstraint] = []
if left {
let constraintLeft = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
constraints.append(constraintLeft)
}
if top {
let constraintTop = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
constraints.append(constraintTop)
}
if right {
let constraintRight = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
constraints.append(constraintRight)
}
if bottom {
let constraintBottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
constraints.append(constraintBottom)
}
self.addConstraints(constraints)
}
}
You can call this method like this:
containerView.setConstraintsFor(contentView: subView!, top: false)
This will add subView to the containerView and constraint to all sides except top side.
You can modify this method to pass left, top, right, bottom Constant value if you want.

View size is automatically being set to window size, when one of window's subview is removed

I have two views one of them is the mainVideoView (Grey color) and the other is the subVideoView (red color). Both of them are the subViews of UIApplication.shared.keyWindow.
When I try and minimise them (using func minimiseOrMaximiseViews, they get minimised (as shown in the below image).
After which, I would like to remove the subVideoView from the window. The moment I try and remove the subVideoView (using func removeSubVideoViewFromVideoView() called from func minimiseOrMaximiseViews), the mainVideoView enlarges to the full screen size, I am not sure why this is happening, I want it to stay at the same size.
Could someone please advise/ suggest how I could achieve this ?
This is how I am setting up the views
func configureVideoView(){
videoView.backgroundColor = UIColor.darkGray
videoView.tag = 0 // To identify view during animation
// ADDING TAP GESTURE TO MAXIMISE THE VIEW WHEN ITS SMALL
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:)))
videoView.addGestureRecognizer(tapGestureRecognizer)
// ADDING PAN GESTURE RECOGNISER TO MAKE VIDEOVIEW MOVABLE INSIDE PARENT VIEW
videoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragView)))
guard let window = UIApplication.shared.keyWindow else{
return
}
window.addSubview(videoView)
videoView.translatesAutoresizingMaskIntoConstraints = false
let viewsDict = ["videoView" : videoView] as [String : Any]
// SETTING CONSTRAINTS
window.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[videoView]|", options: [], metrics: nil, views: viewsDict))
window.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[videoView]|", options: [], metrics: nil, views: viewsDict))
window.layoutIfNeeded() // Lays out subviews immediately
window.bringSubview(toFront: videoView) // To bring subView to front
print("Videoview constraints in configureVideoView \(videoView.constraints)")
}
func configureSubVideoView(){
subVideoView.backgroundColor = UIColor.red
subVideoView.tag = 1 // To identify view during animation
subVideoView.isHidden = true
// Adding Pan Gesture recogniser to make subVideoView movable inside parentview
subVideoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragView)))
// Constraining subVideoView to window to ensure that minimising and maximising animation works properly
constrainSubVideoViewToWindow()
}
func constrainSubVideoViewToWindow(){
//self.subVideoView.setNeedsLayout()
guard let window = UIApplication.shared.keyWindow else{
print("Window does not exist")
return
}
guard !window.subviews.contains(subVideoView)else{ // Does not allow to go through the below code if the window already contains subVideoView
return
}
if self.videoView.subviews.contains(subVideoView){ // If videoView contains subVideoView remove subVideoView
subVideoView.removeFromSuperview()
}
window.addSubview(subVideoView)
// Default constraints to ensure that the subVideoView is initialised with maxSubVideoViewWidth & maxSubVideoViewHeight and is positioned above buttons
let bottomOffset = buttonDiameter + buttonStackViewBottomPadding + padding
subVideoView.translatesAutoresizingMaskIntoConstraints = false
let widthConstraint = NSLayoutConstraint(item: subVideoView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1.0, constant: maxSubVideoViewWidth)
let heightConstraint = NSLayoutConstraint(item: subVideoView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1.0, constant: maxSubVideoViewHeight)
let rightConstraint = NSLayoutConstraint(item: subVideoView, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1.0, constant: -padding)
let bottomConstraint = NSLayoutConstraint(item: subVideoView, attribute: .bottom, relatedBy: .equal, toItem: window, attribute: .bottom, multiplier: 1.0, constant: -bottomOffset)
var constraintsArray = [NSLayoutConstraint]()
constraintsArray.append(widthConstraint)
constraintsArray.append(heightConstraint)
constraintsArray.append(rightConstraint)
constraintsArray.append(bottomConstraint)
window.addConstraints(constraintsArray)
window.layoutIfNeeded() // Lays out subviews immediately
subVideoView.setViewCornerRadius()
window.bringSubview(toFront: subVideoView) // To bring subView to front
}
This is how I am animating views and removing subVideoView
func minimiseOrMaximiseViews(animationType: String){
let window = UIApplication.shared.keyWindow
let buttonStackViewHeight = buttonsStackView.frame.height
if animationType == "maximiseView" {
constrainSubVideoViewToWindow()
}
UIView.animate(withDuration: 0.3, delay: 0, options: [],
animations: { [unowned self] in // "[unowned self] in" added to avoid strong reference cycles,
switch animationType {
case "minimiseView" :
self.hideControls()
// Minimising self i.e videoView
self.videoView.frame = CGRect(x: self.mainScreenWidth - self.videoViewWidth - self.padding,
y: self.mainScreenHeight - self.videoViewHeight - self.padding,
width: self.videoViewWidth,
height: self.videoViewHeight)
// Minimising subVideoView
self.subVideoView.frame = CGRect(x: self.mainScreenWidth - self.minSubVideoViewWidth - self.padding * 2,
y: self.mainScreenHeight - self.minSubVideoViewHeight - self.padding * 2,
width: self.minSubVideoViewWidth,
height: self.minSubVideoViewHeight)
window?.layoutIfNeeded()
self.videoView.setViewCornerRadius()
self.subVideoView.setViewCornerRadius()
print("self.subVideoView.frame AFTER setting: \(self.subVideoView.frame)")
default:
break
}
}) { [unowned self] (finished: Bool) in // This will be called when the animation completes, and its finished value will be true
if animationType == "minimiseView" {
// **** Removing subVideoView from videoView ****
self.removeSubVideoViewFromVideoView()
}
}
}
func removeSubVideoViewFromVideoView(){
//self.subVideoView.setNeedsLayout()
guard let window = UIApplication.shared.keyWindow else{
return
}
guard !self.videoView.subviews.contains(subVideoView)else{ // Does not allow to go through the below code if the videoView already contains subVideoView
return
}
if window.subviews.contains(subVideoView) { // removing subVideoView from window
subVideoView.removeFromSuperview() // *** CAUSE OF ISSUE ***
}
guard !window.subviews.contains(subVideoView) else{
return
}
videoView.addSubview(subVideoView)
}
The videoView and subVideoView are layout by Auto Layout. In Auto Layout if you want to change the frame, you have to update the constraints.
Calling removeFromSuperView will removes any constraints that refer to the view you are removing, or that refer to any view in the subtree of the view you are removing. This means the auto layout system will to rearrange views.

How to add android like toast in iOS?

In android we can add toast directly.
Is their any method to add similar toast in iOS?
I created the transparent view to use as toast but for multiple text sizes i have to create more than one view.
There is no Android type Toast control available in iOS.
If you want to use something like it, you need to customise UIView with UILabel, or use some already created Toast type component, like below:
Android Type Toast custom
You can use MBProgressHUD to show a toast like android. After adding MBProgressHUD you can display a toast by this way
let progressHUD = MBProgressHUD.showAdded(to: self.view, animated: true)
progressHUD.mode = MBProgressHUDMode.text
progressHUD.detailsLabel.text = "Your message here"
progressHUD.margin = 10.0
progressHUD.offset.y = 150.0
progressHUD.isUserInteractionEnabled = false
progressHUD.removeFromSuperViewOnHide = true
progressHUD.hide(animated: true, afterDelay: 3.0)
This is best library I used for showing Toast in iOS apps same as android.
It also has pod support and pod name is pod 'Toast'
And implementation is so simple like
#import <UIView+Toast.h>
in your ViewController and then following line wherever you want to show it
[self.view makeToast:#"YOUR TOAST MESSAGE" duration:TOAST_TIMEOUT position:TOAST_CENTER];
Value for above keys are
#define TOAST_TOP #"CSToastPositionTop"
#define TOAST_CENTER #"CSToastPositionCenter"
#define TOAST_BOTTOM #"CSToastPositionBottom"
#define TOAST_TIMEOUT 2.0
You can use this function to display toast message in iOS. Just create the extension on the view and call this method with the message.
Swift 4
extension UIView {
func displayToast(_ message : String) {
guard let delegate = UIApplication.shared.delegate as? AppDelegate, let window = delegate.window else {
return
}
if let toast = window.subviews.first(where: { $0 is UILabel && $0.tag == -1001 }) {
toast.removeFromSuperview()
}
let toastView = UILabel()
toastView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
toastView.textColor = UIColor.white
toastView.textAlignment = .center
toastView.font = UIFont(name: "Font-name", size: 17)
toastView.layer.cornerRadius = 25
toastView.text = message
toastView.numberOfLines = 0
toastView.alpha = 0
toastView.translatesAutoresizingMaskIntoConstraints = false
toastView.tag = -1001
window.addSubview(toastView)
let horizontalCenterContraint: NSLayoutConstraint = NSLayoutConstraint(item: toastView, attribute: .centerX, relatedBy: .equal, toItem: window, attribute: .centerX, multiplier: 1, constant: 0)
let widthContraint: NSLayoutConstraint = NSLayoutConstraint(item: toastView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: (self.frame.size.width-25) )
let verticalContraint: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(>=200)-[toastView(==50)]-68-|", options: [.alignAllCenterX, .alignAllCenterY], metrics: nil, views: ["toastView": toastView])
NSLayoutConstraint.activate([horizontalCenterContraint, widthContraint])
NSLayoutConstraint.activate(verticalContraint)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
toastView.alpha = 1
}, completion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
toastView.alpha = 0
}, completion: { finished in
toastView.removeFromSuperview()
})
})
}
}
usage:
just call view.displayToast("Hello World") from UIViewController
there is no ThostView in iOS. we Can custom are use 3rd party libraries.
follow this link -
https://github.com/scalessec/Toast-Swift
There is a 3rd party library that supports customizable toast notification with single line of code. Here is a simple example of it:
import Toast_Swift
...
// basic usage
self.view.makeToast("This is a piece of toast")
// toast with a specific duration and position
self.view.makeToast("This is a piece of toast", duration: 3.0, position: .top)
Toast Swift library
Or else,
If you want to implement by your own. Use below code.
let toastLabel = UILabel(frame: CGRectMake(self.view.frame.size.width/2 - 150, self.view.frame.size.height-100, 300, 35))
toastLabel.backgroundColor = UIColor.blackColor()
toastLabel.textColor = UIColor.whiteColor()
toastLabel.textAlignment = NSTextAlignment.Center;
self.view.addSubview(toastLabel)
toastLabel.text = "hello man..."
toastLabel.alpha = 1.0
toastLabel.layer.cornerRadius = 10;
toastLabel.clipsToBounds = true
UIView.animateWithDuration(4.0, delay: 0.1, options: UIViewAnimationOptions.CurveEaseOut, animations: {
toastLabel.alpha = 0.0
})
An attempt to make Suhit Pal's answer above Swift 4 like with a way to remove an existing Toast once a new appears
extension UIView {
private static var toastView: UILabel? = nil
func displayToast(message : String, duration: Double? = 3.0) -> Void {
if UIView.toastView != nil {
UIView.toastView!.removeFromSuperview()
}
UIView.toastView = UILabel()
UIView.toastView!.backgroundColor = UIColor.black.withAlphaComponent(0.7)
UIView.toastView!.textColor = UIColor.white
UIView.toastView!.textAlignment = .center
UIView.toastView!.font = UIFont(name: "Font-name", size: 17)
UIView.toastView!.layer.masksToBounds = true
UIView.toastView!.layer.cornerRadius = 25
UIView.toastView!.text = message
UIView.toastView!.numberOfLines = 0
UIView.toastView!.alpha = 0
UIView.toastView!.translatesAutoresizingMaskIntoConstraints = false
let window = UIApplication.shared.delegate?.window!
window?.addSubview(UIView.toastView!)
let horizontalCenterContraint : NSLayoutConstraint = NSLayoutConstraint(item: UIView.toastView!, attribute: .centerX, relatedBy: .equal, toItem: window, attribute: .centerX, multiplier: 1, constant: 0)
let widthContraint: NSLayoutConstraint = NSLayoutConstraint(item: UIView.toastView!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: (self.frame.size.width-25) )
let verticalContraint: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(>=200)-[loginView(==50)]-68-|", options: [.alignAllCenterX, .alignAllCenterY], metrics: nil, views: ["loginView": UIView.toastView!])
NSLayoutConstraint.activate([horizontalCenterContraint, widthContraint])
NSLayoutConstraint.activate(verticalContraint)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
UIView.toastView!.alpha = 1
}, completion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + duration!) {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
UIView.toastView!.alpha = 0
}, completion: { (_) in
UIView.toastView!.removeFromSuperview()
})
}
}
}
Here is my latest version of this. It allows to cancel an existing toast by just pushing a new. It also allows to provide an optional "reverseColors" parameter, which writes white on black instead default black on white. The toast adapts to the text size with a little margin left and right.
extension UIView {
private static var toastView: UILabel? = nil
private static var toastViewCancelTask : DispatchWorkItem?
func displayToast(message : String, duration: Double, reverseColors: Bool? = false) -> Void {
if UIView.toastView != nil {
UIView.toastView!.removeFromSuperview()
UIView.toastViewCancelTask?.cancel()
}
UIView.toastView = UILabel()
UIView.toastViewCancelTask = DispatchWorkItem {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
UIView.toastView!.alpha = 0
}, completion: { (_) in
UIView.toastView!.removeFromSuperview()
})
}
let toastView = UIView.toastView!
print(message)
if reverseColors != nil && reverseColors! {
toastView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
toastView.textColor = UIColor.white
}
else {
toastView.backgroundColor = UIColor.white.withAlphaComponent(0.7)
toastView.textColor = UIColor.black
}
toastView.textAlignment = .center
toastView.font = UIFont.systemFont(ofSize: 17)
toastView.layer.masksToBounds = true
toastView.layer.cornerRadius = 12
toastView.text = message
toastView.numberOfLines = 0
toastView.alpha = 0
toastView.sizeToFit()
toastView.translatesAutoresizingMaskIntoConstraints = false
let width = toastView.frame.size.width + 100 > self.frame.size.width ? self.frame.size.width - 100 : toastView.frame.size.width + 100
let window = UIApplication.shared.delegate?.window!
window?.addSubview(toastView)
let horizontalCenterContraint : NSLayoutConstraint = NSLayoutConstraint(item: toastView, attribute: .centerX, relatedBy: .equal, toItem: window, attribute: .centerX, multiplier: 1, constant: 0)
let widthContraint: NSLayoutConstraint = NSLayoutConstraint(item: toastView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: width )
let verticalContraint: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(>=200)-[loginView(==50)]-30-|", options: [.alignAllCenterX, .alignAllCenterY], metrics: nil, views: ["loginView": toastView])
NSLayoutConstraint.activate([horizontalCenterContraint, widthContraint])
NSLayoutConstraint.activate(verticalContraint)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
toastView.alpha = 0.8
}, completion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + duration , execute: UIView.toastViewCancelTask!)
}
}

UITableView damping animation and layout constraints

I'm trying to animate UITableView to act like a dropdownMenu by using its height constraint and UIView.animateWithDamping(..) block. I'm occuring weird problem with white background under tableView.
iPhone Simulator showing the problem
I have cleared each background color and it doesn't help much.
Here is the code setting all subviews of dropDownView, which is a UIView:
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.elements = []
defaultSetup()
}
private func defaultSetup() {
configureActionButton()
configureTableView()
}
private func configureActionButton() {
actionButton = UIButton(frame: CGRectZero)
actionButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(actionButton)
guard let superview = actionButton.superview else {
assert(false, "ActionButton adding to superview failed.")
return
}
// Constraints
actionButton.constrain(.Leading, .Equal, superview, .Leading, constant: 0, multiplier: 1)?.constrain(.Trailing, .Equal, superview, .Trailing, constant: 0, multiplier: 1)?.constrain(.Top, .Equal, superview, .Top, constant: 0, multiplier: 1)?.constrain(.Bottom, .Equal, superview, .Bottom, constant: 0, multiplier: 1)
// Appearance
actionButton.backgroundColor = UIColor.clearColor()
actionButton.opaque = false
actionButton.contentHorizontalAlignment = .Left
actionButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
if borderVisible {
actionButton.layer.cornerRadius = 5
actionButton.layer.borderColor = UIColor.blackColor().CGColor
actionButton.layer.borderWidth = 1
actionButton.clipsToBounds = true
}
// Actions
actionButton.addTarget(self, action: "menuAction:", forControlEvents: .TouchUpInside)
}
private func configureTableView() {
tableView = BOTableView(frame: CGRectZero, items: elements, configuration: configuration)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
addSubview(tableView)
guard let tableViewSuperview = tableView.superview else {
assert(false, "TableView adding to superview failed.")
return
}
// Constraints
tableView.constrain(.Trailing, .Equal, tableViewSuperview, .Trailing, constant: 0, multiplier: 1)?.constrain(.Top, .Equal, tableViewSuperview, .Bottom, constant: 0, multiplier: 1)?.constrain(.Leading, .Equal, tableViewSuperview, .Leading, constant: 0, multiplier: 1)
tvHeightConstraint = NSLayoutConstraint(item: tableView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0)
tableView.addConstraint(tvHeightConstraint)
}
BOTableView class initializer:
init(frame: CGRect, items: [String], configuration: BOConfiguration) {
super.init(frame: frame, style: UITableViewStyle.Plain)
self.items = items
self.selectedIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.configuration = configuration
// Setup table view
self.opaque = false
self.backgroundView?.backgroundColor = UIColor.clearColor()
self.backgroundColor = UIColor.clearColor()
self.separatorColor = UIColor.blackColor()
self.scrollEnabled = false
self.separatorStyle = .SingleLine
self.layer.cornerRadius = 5
self.layer.borderColor = UIColor.blackColor().CGColor
self.layer.borderWidth = 1
self.clipsToBounds = true
}
UIView animations:
private func showMenuWithCompletionBlock(completion: (succeeded: Bool) -> Void) {
delegate?.menuWillShow(self)
let tvHeight = frame.size.height * CGFloat(elements.count)
tvHeightConstraint.constant = tvHeight
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.5, options: .CurveEaseInOut, animations: { [weak self] () -> Void in
guard let strongSelf = self else {
completion(succeeded: false)
return
}
strongSelf.layoutIfNeeded()
}, completion: { (finished) -> Void in
if finished {
completion(succeeded: true)
}
})
}
Here is the code for UIView + Constraints extension, used in code:
extension UIView {
/**
:returns: true if v is in this view's super view chain
*/
public func isSuper(v : UIView) -> Bool
{
for var s : UIView? = self; s != nil; s = s?.superview {
if(v == s) {
return true;
}
}
return false
}
public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, _ otherView: UIView, _ otherAttribute: NSLayoutAttribute, constant: CGFloat = 0.0, multiplier : CGFloat = 1.0) -> UIView?
{
let c = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: otherView, attribute: otherAttribute, multiplier: multiplier, constant: constant)
if isSuper(otherView) {
otherView.addConstraint(c)
return self
}
else if(otherView.isSuper(self) || otherView == self)
{
self.addConstraint(c)
return self
}
assert(false)
return nil
}
public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, constant: CGFloat, multiplier : CGFloat = 1.0) -> UIView?
{
let c = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: nil, attribute: .NotAnAttribute, multiplier: multiplier, constant: constant)
self.addConstraint(c)
return self
}
}
When I tried to debug the views' hierarchy in debugger, the only view which had white background was tableView, but I have cleared the background in code. I have also tried to set tableView's backgroundView to nil as well as backgroundView.backgroundColor to clearColor(). Nothing changed.
Maybe try to set the UITableView footer to a blank view, don't really know why, but it seams to help for similar issue like You have.
[_tableView setTableFooterView:[[UIView alloc] init]];

iOS .addConstraint only works once

I'm making a custom keyboard for iOS8 and on the Apple Developer docs it says that you can change the height of a custom keyboard anytime after the initial primary view draws on the screen. And it says to do this you should use the .addConstaint() method.
Here's a link:
https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/Keyboard.html
I'm using Swift. The initial height of the keyboard is 215 pixels. I have a swipe up gesture that increases the height to 350 pixels. Which works as expected. And a swipe down that changes the height to 300 pixels.
That all works ok, but the problem is it only works once. I swipe up and the height increases, I swipe down and it decreases, but if I swipe up again nothing happens. If I swipe down again nothing happens.
So I'd be greatful if anyone could take a look at my two functions and tell me what I'm doing wrong.
Here's the code:
// IBActions
#IBAction func action1(sender: AnyObject) {
if topboxvisible == false {
topboxvisible = true
UIView.animateWithDuration(0.08, delay: 0, options: .CurveEaseIn, animations: {
self.topbox.frame.offset(dx: 0, dy: 40)
}, completion: nil)
}
let expandedHeight:CGFloat = 300
let heightConstraint = NSLayoutConstraint(item:self.view,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 0.0,
constant: expandedHeight)
self.view.removeConstraint(heightConstraint)
self.view.addConstraint(heightConstraint)
}
#IBAction func action2(sender: AnyObject) {
if topboxvisible == true {
topboxvisible = false
UIView.animateWithDuration(0.08, delay: 0, options: .CurveEaseOut, animations: {
self.topbox.frame.offset(dx: 0, dy: -40)
}, completion: nil)
}
let expandedHeight:CGFloat = 350
let heightConstraint = NSLayoutConstraint(item:self.view,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 0.0,
constant: expandedHeight)
self.view.removeConstraint(heightConstraint)
self.view.addConstraint(heightConstraint)
}
Your removeConstraint calls aren't doing anything, because the constraint you just created (in the previous line of code) isn't in the view. So what you're doing here is adding more constraints every time, from what I can see. I imagine this is leading to multiple conflicting constraints—do you see any NSConstraint warnings popping out in the console log while you're running the code?
Try this: create both your height constraints in advance, and store them in instance variables. In action1, remove the 350-high constraint, and add the 300. In action2, remove the 300, and add the 350.

Resources