I have a viewController where am showing image for adding the zooming functionality I added the scrollView in viewController and inside of ScrollView I added ImageView everything is working fine expect of one thing, am hiding, and showing the bars (navigation bar + tab bar) on tap but when hiding them my imageView moves upside see the below images
See here's the image and the bars.
Here I just tapped on the view and bars got hidden but as you can see my imageView is also moved from its previous place, this is what I want to solve I don't want to move my imageView.
This is how am hiding my navigation bar:
func tabBarIsVisible() ->Bool {
return self.tabBarController?.tabBar.frame.origin.y < CGRectGetMaxY(self.view.frame)
}
func toggle(sender: AnyObject) {
navigationController?.setNavigationBarHidden(navigationController?.navigationBarHidden == false, animated: true)
setTabBarVisible(!tabBarIsVisible(), animated: true)
}
any idea how can I hide and show the bars without affecting my other Views?
The problem is that you need to set the constraint of your imageView to your superView, not TopLayoutGuide or BottomLayoutGuide.
like so:
Then you can do something like this to make the it smootly with animation:
import UIKit
class ViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
var barIsHidden = false
var navigationBarHeight: CGFloat = 0
var tabBarHeight: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.hideAndShowBar))
view.addGestureRecognizer(tapGesture)
navigationBarHeight = (self.navigationController?.navigationBar.frame.size.height)!
tabBarHeight = (self.tabBarController?.tabBar.frame.size.height)!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func hideAndShowBar() {
print("tap!!")
if barIsHidden == false {
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseOut, animations: {
// fade animation
self.navigationController?.navigationBar.alpha = 0.0
self.tabBarController?.tabBar.alpha = 0.0
// set height animation
self.navigationController?.navigationBar.frame.size.height = 0.0
self.tabBarController?.tabBar.frame.size.height = 0.0
}, completion: { (_) in
self.barIsHidden = true
})
} else {
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseOut, animations: {
// fade animation
self.navigationController?.navigationBar.alpha = 1.0
self.tabBarController?.tabBar.alpha = 1.0
// set height animation
self.navigationController?.navigationBar.frame.size.height = self.navigationBarHeight
self.tabBarController?.tabBar.frame.size.height = self.tabBarHeight
}, completion: { (_) in
self.barIsHidden = false
})
}
}
}
Here is the result:
I have created an example project for you at: https://github.com/khuong291/Swift_Example_Series
You can see it at project 37
Hope this will help you.
Related
I want to show the View for a few seconds and make it disappear from the screen.
If I wait 10 seconds on this screen
I want to make this view go off screen.
How can I animate the view slowly descending down?
code...
fileprivate func setupViewToAnimate(view: UIView) {
UIView.animate(withDuration: 2, delay: 10, options: .curveEaseInOut , animations: {
}) { _ in
}
}
You can use Timer to schedule the dismiss of your snackBar/notification view. it will be triggered after the completion of animation for showing your view. Please check the following code for the same.
private let keyWindow = UIApplication.shared.keyWindow!
let snackbarView = UIView()
fileprivate func setupViewToAnimate() {
let targetYPost = snackbarView.frame.origin.y
let dismissTimerLength = 3.0
snackbarView.frame.origin.y = keyWindow.frame.height
UIView.animate(withDuration: 0.4, animations: {
self.snackbarView.frame.origin.y = targetYPost
}, completion: {
_ in
Timer.scheduledTimer(timeInterval: TimeInterval(dismissTimerLength), target: self, selector: #selector(self.hideSnackbarView), userInfo: nil, repeats: false)
})
}
//After 3.4 sec snack bar will be hidden
#objc private func hideSnackbarView() {
UIView.animate(withDuration: 0.4, animations: {
self.snackbarView.frame.origin.y = self.keyWindow.frame.height
}, completion: {
_ in
self.snackbarView.isHidden = true
})
}
Output:-
I would suggest something like this:
private func setupViewToAnimate(view: UIView, bottomConstraint: NSLayoutConstraint) {
UIView.animate(withDuration: 2, delay: 10, options: .curveEaseInOut , animations: {
// Set the bottomConstraint to minus the height of the view so it gets entierly hidden
bottomConstraint.constant = -view.frame.height
// Tell the view to re-layout its subviews again so we have something to animate
self.view.layoutIfNeeded()
}) { _ in
// If the animation is completed, we can remove the view from the superview since we don't need it anymore
view.removeFromSuperview()
}
}
I'm attempting to smoothly transition between two images by using a UIImageView and by animating the transition, but it seems that the transition isn't working as expected as a white screen appears instead of the new image fading in.
I've used https://stackoverflow.com/a/42710002/7908770 for reference and can't figure out where I've gone wrong :(
class ViewController: UIViewController {
var bckgImageView = UIImageView(image: UIImage(named: "one"))
override func viewDidLoad() {
super.viewDidLoad()
bckgImageView.frame = self.view.frame
self.view.addSubview(bckgImageView)
animateBackground()
}
func animateBackground() {
UIView.transition(with: self.view,
duration: 5,
options: .transitionCrossDissolve,
animations: { self.bckgImageView.image = UIImage(named: "two") },
completion: nil)
}
AutoLayout not calculated yet when in viewDidLoad
You can set frame when layout changed in viewDidLayoutSubviews.
Try this
class ViewController: UIViewController {
var bckgImageView = UIImageView(image: UIImage(named: "one"))
override func viewDidLoad() {
super.viewDidLoad()
bckgImageView.frame = self.view.frame
self.view.addSubview(bckgImageView)
animateBackground()
}
func animateBackground() {
UIView.transition(with: self.view,
duration: 5,
options: .transitionCrossDissolve,
animations: { self.bckgImageView.image = UIImage(named: "two") },
completion: nil)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
bckgImageView.frame = self.view.frame
}
}
First, you're setting the frame of the image view to the frame of the "root" view, which is not-yet defined in viewDidLoad(). You're much better off using constraints and letting auto-layout size the view.
Second, if you are starting your animation in viewDidLoad() or even in viewDidLayoutSubviews(), the screen is not yet visible --- so you don't see the initial image (or you only see it very briefly, if the animation is short).
So, start your animation at viewDidAppear(), or, give it a little delay, as in the following:
class ViewController: UIViewController {
var bckgImageView = UIImageView(image: UIImage(named: "one"))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(bckgImageView)
bckgImageView.translatesAutoresizingMaskIntoConstraints = false
// constrain image view to all 4 sides
NSLayoutConstraint.activate([
bckgImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
bckgImageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bckgImageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
bckgImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// start animation after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
self.animateBackground()
})
}
func animateBackground() {
UIView.transition(with: self.view,
duration: 5,
options: .transitionCrossDissolve,
animations: { self.bckgImageView.image = UIImage(named: "two") },
completion: nil)
}
}
override func viewDidLoad() {
let tap = UITapGestureRecognizer(target: self, action: #selector(touchHandled))
view.addGestureRecognizer(tap)
}
#objc func touchHandled() {
tabBarController?.hideTabBarAnimated(hide: true)
}
extension UITabBarController {
func hideTabBarAnimated(hide:Bool) {
UIView.animate(withDuration: 2, animations: {
if hide {
self.tabBar.transform = CGAffineTransform(translationX: 0, y: 100)
} else {
self.tabBar.transform = CGAffineTransform(translationX: 0, y: -100)
}
})
}
}
I can only hide the tab bar but I can't make it show when you tap again. I tried to look for answers on stack overflow but the answers seems to only work if you're using a button or a storyboard.
Have a variable isTabBarHidden in class which stores if the tabBar has been animated to hide. (You could have used tabBar.isHidden, but that would complicate the logic a little bit when animate hiding and showing)
class ViewController {
var isTabBarHidden = false // set the default value as required
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(touchHandled))
view.addGestureRecognizer(tap)
}
#objc func touchHandled() {
guard let tabBarControllerFound = tabBarController else {
return
}
tabBarController?.hideTabBarAnimated(hide: !isTabBarHidden)
isTabBarHidden = !isTabBarHidden
}
}
Generalised solution with protocol which will work in all the screens
Create UIViewController named BaseViewController and make it base class of all of your view controllers
Now Define protocol
protocol ProtocolHideTabbar:class {
func hideTabbar ()
}
protocol ProtocolShowTabbar:class {
func showTabbar ()
}
extension ProtocolHideTabbar where Self : UIViewController {
func hideTabbar () {
self.tabBarController?.tabBar.isHidden = true
}
}
extension ProtocolShowTabbar where Self : UIViewController {
func showTabbar () {
self.tabBarController?.tabBar.isHidden = false
}
}
By default we want show tabbar in every view controller so
extension UIViewController : ProtocolShowTabbar {}
In your BaseView Controller
in view will appear method add following code to show hide based on protocol
if self is ProtocolHideTabbar {
( self as! ProtocolHideTabbar).hideTabbar()
} else if self is ProtocolShowTabbar{
( self as ProtocolShowTabbar).showTabbar()
}
How to use
Simply
class YourViewControllerWithTabBarHidden:BaseViewController,ProtocolHideTabbar {
}
Hope it is helpful
Tested 100% working
Please try below code for that in UITabBarController subclass
var isTabBarHidden:Bool = false
func setTabBarHidden(_ tabBarHidden: Bool, animated: Bool,completion:((Void) -> Void)? = nil) {
if tabBarHidden == isTabBarHidden {
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
//check tab bar is visible and view and window height is same then it should be 49 + window Heigth
if (tabBarHidden == true && UIScreen.main.bounds.height == self.view.frame.height) {
let offset = self.tabBar.frame.size.height
self.view.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height + offset)
}
if let block = completion {
block()
}
return
}
let offset: CGFloat? = tabBarHidden ? self.tabBar.frame.size.height : -self.tabBar.frame.size.height
UIView.animate(withDuration: animated ? 0.250 : 0.0, delay: 0.1, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [.curveEaseIn, .layoutSubviews], animations: {() -> Void in
self.tabBar.center = CGPoint(x: CGFloat(self.tabBar.center.x), y: CGFloat(self.tabBar.center.y + offset!))
//Check if View is already at bottom so we don't want to move view more up (it will show black screen on bottom ) Scnario : When present mail app
if (Int(offset!) <= 0 && UIScreen.main.bounds.height == self.view.frame.height) == false {
self.view.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height + offset!)
}
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}, completion: { _ in
if let block = completion {
block()
}
})
isTabBarHidden = tabBarHidden
}
Hope it is helpful
I'm looking for the best way to invert the place of two UIView, with animation if possible (first i need to change the place, animation is optionnal).
viewController :
So, i want view1 to invert his place with the view2. The views are set with autolayout in the storyboard at the init.
if state == true {
view1 at the top
view2 at the bot
} else {
view 2 at the top
view 1 at the top
}
I've tried to take view.frame.(x, y, width, height) from the other view and set it to the view but it doesn't work.
I think the best way to do this would be to have a topConstraint for both views connected to the header and then change their values and animate the transition. You can do it in a way similar to this:
class MyViewController: UIViewController {
var view1TopConstraint: NSLayoutConstraint!
var view2TopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view1TopConstraint = view1.topAnchor.constraintEqualToAnchor(header.bottomAnchor, constant: 0)
view1TopConstraint.isActive = true
view2TopConstraint = view2.topAnchor.constraintEqualToAnchor(header.bottomAnchor, constant: view1.frame.height)
view2TopConstraint.isActive = true
}
func changeView2ToTop() {
UIView.animateWithDuration(0.2, animations: { //this will animate the change between 2 and 1 where 2 is at the top now
self.view2TopConstraint.constant = 0
self.view1TopConstraint.constant = view2.frame.height
//or knowing that view2.frame.height represents 30% of the total view frame you can do like this as well
// self.view1TopConstraint.constant = view.frame.height * 0.3
self.view.layoutIfNeeded()
})
}
You could also create the NSLayoutConstraint in storyboard and have an outlet instead of the variable I have created or set the top constraint for both views in storyboard at "remove at build time" if you are doing both. This way you won't have 2 top constraints and no constraint warrning
I Made an Example: https://dl.dropboxusercontent.com/u/24078452/MovingView.zip
I just created a Storyboard with 3 View, Header, View 1 (Red) and View 2 (Yellow). Then I added IBoutlets and animated them at viewDid Appear, here is the code:
import UIKit
class ViewController: UIViewController {
var position1: CGRect?
var position2: CGRect?
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
position1 = view1.frame
position2 = view2.frame
UIView.animate(withDuration: 2.5) {
self.view1.frame = self.position2!
self.view2.frame = self.position1!
}
}
}
Hello #Makaille I have try to resolve your problem.
I have made an example, which will help you for your required implementation.
Check here: Source Code
I hope, it will going to help you.
#IBAction func toggle(_ sender: UIButton) {
sender.isUserInteractionEnabled = false
if(sender.isSelected){
let topPinConstantValue = layout_view2TopPin.constant
layout_view1TopPin.constant = topPinConstantValue
layout_view2TopPin.priority = 249
layout_view1BottomPin_to_View2Top.priority = 999
layout_view1TopPin.priority = 999
layout_view2BottomPin_ToView1Top.priority = 249
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (value) in
if(value){
sender.isUserInteractionEnabled = true
}
})
} else {
let topPinConstantValue = layout_view1TopPin.constant
layout_view2TopPin.constant = topPinConstantValue
layout_view1TopPin.priority = 249
layout_view2BottomPin_ToView1Top.priority = 999
layout_view2TopPin.priority = 999
layout_view1BottomPin_to_View2Top.priority = 249
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (value) in
if(value){
sender.isUserInteractionEnabled = true
}
})
}
sender.isSelected = !sender.isSelected
}
When you scroll the body on iOS 10 Safari the bottom controls hide. Can I prevent that?
I need body to be scrollable.
Here is the code
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translationInView(scrollView).y < 0{
changeTabBar(true, animated: true)
}
else{
changeTabBar(false, animated: true)
}
}
You can also use the other callback method:
func scrollViewDidScroll(scrollView: UIScrollView) {
...
}
but if you choose so, then you most handle multiple calls to the helper method that actually hides the tabBar.
And then you need to add this method that animates the hide/show of the tabBar.
func changeTabBar(hidden:Bool, animated: Bool){
var tabBar = self.tabBarController?.tabBar
if tabBar!.hidden == hidden{ return }
let frame = tabBar?.frame
let offset = (hidden ? (frame?.size.height)! : -(frame?.size.height)!)
let duration:NSTimeInterval = (animated ? 0.5 : 0.0)
tabBar?.hidden = false
if frame != nil
{
UIView.animateWithDuration(duration,
animations: {tabBar!.frame = CGRectOffset(frame!, 0, offset)},
completion: {
println($0)
if $0 {tabBar?.hidden = hidden}
})
}
}