Start animation once a UIView appeared on screen - swift

I have a custom UIView that I want to cover the screen once the user taps a button. It kind of simulates a custom view. There is child UIView in the custom UIView that should animate from the bottom (hidden at first) up to it's normal position (visible at the bottom). The problem that I am having is that it seems like layoutSubviews is a bad place to start doing animations. Where would the correct place be? Something like viewDidAppear but for UIViews.
In UIViewController:
let rect: CGRect = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height)
let alertView = AlertView(frame: rect)
view.addSubview(alertView)
In the AlertView:
import UIKit
class AlertView: UIView {
let nibName = "AlertView"
let animationDuration = 0.5
var view: UIView!
#IBOutlet weak var notificationView: UIView!
#IBOutlet weak var notificationBottomConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
viewSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
viewSetup()
}
override func didAddSubview(subview: UIView) {
super.didAddSubview(subview)
}
func viewSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
// move the notification view offscreen
notificationBottomConstraint.constant = -notificationView.frame.size.height
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiateWithOwner(self, options: nil)[0] as! UIView
}
override func layoutSubviews() {
super.layoutSubviews()
print("layoutSubviews")
animate()
}
func animate() {
// move the notification up
self.notificationBottomConstraint.constant = 0
UIView.animateWithDuration(animationDuration) { () -> Void in
self.view.setNeedsDisplay()
}
}
}

I suggest you to call your animate() function from one of these methods:
willMoveToSuperview:
didMoveToSuperview
These methods of UIView as needed to track the movement of the current view in your view hierarchy.
Reference: UIView class

Your final constraint value is not in your animate closure. Try this:
func animate() {
// move the notification up
UIView.animateWithDuration(animationDuration) { () -> Void in
self.notificationBottomConstraint.constant = 0
self.view.setNeedsDisplay()
}
}

Related

Call to IBOutlet in custom UIView from another class

I am looking for a way to refer to the IBOutlet variable in a custom class UIView, from another class. I found layoutSubviews, but each change only works on the first call, and not on each subsequent call. Thanks for help!
ViewController class:
var SB = StatusBar()
SB.update(1)
SB.update(2)
SB.update(3)
StatusBar class:
class StatusBar: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var label: UILabel!
var ActualStatus: Int!
required init?(coder: NSCoder) {
super.init(coder: coder)
xibSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
func update(status) {
ActualStatus = status
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
label.text = ActualStatus
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of:self))
let nib = UINib(nibName: "StatusBar", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
Result: label.text is 1, change only works on the first call

Custom Class UIView - not changed

Why in the code below, text changes as value, but there is no change in UIVIew. How do I refresh the data in a custom class? The value changes but does not refresh in the view. I have no ideas anymore.
ViewController.swift:
let SB = StatusBar()
SB.update()
StatusBar.swift:
class StatusBar: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var Status: UITextField!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
func update() {
Status.text = "ok"
print(Status.text)
print("status updated.")
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of:self))
let nib = UINib(nibName: "StatusBar", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
You are not calling update(). In xibSetup(), add update()

How can i see with #designable this design on storyboard

I'm using masking.
My Code:
class ViewController: UIViewController {
#IBOutlet weak var firstImageView: UIImageView!
#IBOutlet weak var seconImageView: UIImageView!
let maskView = UIImageView()
let maskView2 = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
maskView.image = UIImage(named: "Up")
maskView2.image = UIImage(named: "Down")
firstImageView.mask = maskView
seconImageView.mask = maskView2
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
maskView.frame = firstImageView.bounds
maskView2.frame = seconImageView.bounds
}
}
UIImageView Code:
UIImageView class. I can't see this design. How can I do ? I added custom class but didn't work
#IBDesignable
class FirstImageView: UIImageView {
var maskkView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
layoutSubviews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
maskkView.image = UIImage(named: "mask")
mask = maskkView
maskkView.frame = bounds
}
}
Storyboard:
Simulator:
How can i see with #designable this design on storyboard.
I added custom class but didn't work
I assure you that masking in layoutSubviews of an IBDesignable UIImageView does work. Example:
#IBDesignable
class MyImageView : UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
let lay = CAShapeLayer()
lay.frame = self.bounds
lay.path = UIBezierPath(ovalIn: self.bounds).cgPath
self.layer.mask = lay
}
}
Result:
I suspect that the problem with your code is that you are attempting to load an image from the bundle with UIImage(named:) in your IBDesignable code; my guess is that that isn't going to work. IBDesignable code is just some stuff that runs at storyboard rendering time; it cannot behave as if it were truly the running app.

Connect programmatically created view to IB

Following this answer, I managed to add half-modally presented ViewControllerB to ViewControllerA. However, in the example, ViewControllerA is just a red view. I would like to connect it to IB in order to customize it. I tried creating a view controller in IB of class ViewControllerB and connect one of its views to #IBOutlet var menuView: UIView! instead of creating menuView programmatically (as in the example). However, nil was found when adding menuView to the view as a subview. Any help would be great. Thanks!
A couple of observations:
In that example, there is a line that says:
let vc = ViewControllerB()
You could define this view controller’s view in a NIB and this would work fine. But if you’re using storyboards, replace that with ...
let vc = storyboard!.instantiateViewController(withIdentifier: “identifier")
... where identifier is some string that you supplied as the “Storyboard ID” in the “Identity inspector” in IB for that scene.
If this is being triggered by a button or something like that, you can also remove this gotoVCB in that sample, and just do the modal presentation right in IB like usual (e.g. control-dragging from the button to the next scene) and choosing “Present modally”. But you’d have to make sure that you configured all init methods, e.g.:
class ViewControllerB: UIViewController {
// used if you use NIBs or just call `init()`
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
configure()
}
// used if you use storyboards
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
modalPresentationStyle = .custom
transitioningDelegate = self
}
...
}
Unrelated to the immediate question, I might suggest splitting up that answer’s code into separate objects to prevent view controller “bloat”, with better separation of responsibilities between a transitioning delegate object, the animation controller, etc.
For example, maybe:
class ViewControllerB: UIViewController {
let customTransitioningDelegate = PopUpMenuTransitioningDelegate()
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
modalPresentationStyle = .custom
transitioningDelegate = customTransitioningDelegate
}
}
And
class PopUpMenuTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PopUpMenuAnimationController(transitionType: .presenting)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PopUpMenuAnimationController(transitionType: .dismissing)
}
}
And
class PopUpMenuAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case presenting
case dismissing
}
let transitionType: TransitionType
init(transitionType: TransitionType) {
self.transitionType = transitionType
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toVC = transitionContext.viewController(forKey: .to),
let fromVC = transitionContext.viewController(forKey: .from) else { return }
switch transitionType {
case .presenting:
containerView.addSubview(toVC.view)
var rect = fromVC.view.bounds
let menuHeight = fromVC.view.bounds.height / 2
rect.origin.y += fromVC.view.bounds.height
rect.size.height = menuHeight
toVC.view.frame = rect
toVC.view.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
rect.origin.y = menuHeight
toVC.view.frame = rect
toVC.view.alpha = 1
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
case .dismissing:
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
var rect = fromVC.view.frame
rect.origin.y = toVC.view.bounds.height
fromVC.view.frame = rect
fromVC.view.alpha = 0
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
}
That decouples the transitioning delegate and animation controller from the view controller and lets you use this with other view controllers should you ever need to.
Note, I’m also a little more careful about the parameter to completeTransition, too. Once you start playing around with interactive transitions, you might support cancelable transitions.

Hide Custom View UIButton From UIViewController Class

Actually i have a Custom view with two button, and i want to hide it at runtime through UIViewController , So i don't get any exact thing to hide that button from UIViewcontroller class
Here is my CustomView class,
import UIKit
class BottomButtonUIView: UIView {
#IBOutlet weak var btnNewOrder: UIButton!
#IBOutlet weak var btnChat: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = Bundle.main.loadNibNamed("BottomButtonUIView", owner: self, options: nil)?.first as? BottomButtonUIView {
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
}
#IBAction func btnOrderNowClick(_ sender: Any) {
let VC1 = StoryBoardModel.orderDeatalStoryBord.instantiateViewController(withIdentifier: "NewOrderViewController") as! NewOrderViewController
VC1.isPush = false
let navController = UINavigationController(rootViewController: VC1) // Creating a navigation controller with VC1 at the root of the navigation stack.
let currentController = getCurrentVC.getCurrentViewController()
currentController?.present(navController, animated:true, completion: nil)
}
#IBAction func btnChatNowClick(_ sender: Any) {
}
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
}
I set it to UIView in StoryBoard, and then I create outlet of that view,
#IBOutlet weak var viewBottmNewOrder: BottomButtonUIView!
Now i want to hide btnNewOrder from UIViewcontroller class but when i use
viewBottmNewOrder.btnNewOrder.isHidden = true it cause null exception, Please do need full answer.
Please don't do like that. The required init(coder aDecoder: NSCoder) will call a lot of times when the BottomButtonUIView created from xib. And your custom view will look like:
[BottomButtonUIView [ BottomButtonUIView [btnNewOrder, btnChat]]].
So when you access to btnNewOrder like that:
viewBottmNewOrder.btnNewOrder it will null.
I think you should add your custom view in viewDidLoad of your `UIViewController'.