I have a UIView, inside this view I have UIImageView. Also I have a button inside this UIView. What I want to do is when I click this button I want to make a flip animation and remove my UIImageView and load another view into this super view. In my button click even I did something like this
func shareClick()
{
print("SHARE CLICK")
if showingBack {
UIView.transitionWithView(shareView, duration: 1.0, options: .TransitionFlipFromRight, animations: {
self.imgVwTop.removeFromSuperview()
}, completion: nil)
showingBack=false
}
else
{
UIView.transitionWithView(imgVwTop, duration: 1.0, options: .TransitionFlipFromRight, animations: {
self.shareView.removeFromSuperview()
}, completion: nil)
showingBack=true
}
}
I'm confused with the ebhaviour and don't understand exactly how to do it. This button click event doing nothing here.
You can do the following (I assume that share view contains the image view and the button):
override func viewDidLoad()
{
super.viewDidLoad()
shareView = UIView(frame: CGRectMake(30, 100, 300, 400)) //set the frame of the holder view
flippedView = UIView(frame: shareView!.bounds) //setup flipped view
flippedView!.backgroundColor = UIColor.redColor() //for test
isFlipped = false //initially not flipped
//set up the initial view with image and button
aImageView = UIImageView(frame: shareView!.bounds)
aImageView!.image = UIImage(named: "1.jpg")
shareButton = UIButton(type: .System)
shareButton!.setTitle("share", forState: .Normal)
shareButton!.frame = CGRectMake(0, 0, 150, 50)
shareButton!.addTarget(self, action: "shareButtonAction", forControlEvents: .TouchUpInside)
//add both imageview and button to holder view
shareView!.addSubview(aImageView!)
shareView!.addSubview(shareButton!)
//finally add holder to self view
self.view.addSubview(shareView!)
}
Here, you can't remove the super view of the image view if you use the transitionWithView method. The best you can do is replace the image view with new view that you want to show after being flipped. Once again, you can flip back to the image view by adding it as subview. For example:
func shareButtonAction()
{
if (self.isFlipped! == false)
{
UIView.transitionWithView(shareView!, duration: 0.5, options:.TransitionFlipFromRight, animations: { () -> Void in
// self.aImageView!.image = UIImage(named: "2.jpg")
//hear remove the imageview add new view, say flipped view
self.aImageView!.removeFromSuperview()
self.shareView!.addSubview(self.flippedView!)
}, completion: { (Bool) -> Void in
self.isFlipped! = true
self.shareView!.bringSubviewToFront(self.shareButton!) //button should be top of the holder view
})
}
else
{
UIView.transitionWithView(shareView!, duration: 0.5, options:.TransitionFlipFromRight, animations: { () -> Void in
//move back, remove flipped view and add the image view
self.flippedView!.removeFromSuperview()
self.shareView!.addSubview(self.aImageView!)
}, completion: { (Bool) -> Void in
self.isFlipped! = false
self.shareView!.bringSubviewToFront(self.shareButton!)
})
}
}
So it looks like you have the right idea, I believe you have things set up correctly with the view you want to flip in some sort of container.
I think it would help if you instantiated both your views programmatically, in your case a UIImageView and a Button. When you transition the other view will become unloaded because they are listed as weak so best to create them on the fly.
I made up a view controller to test the idea, initially the currentView will be instantiated from the storyboard, and then after that would be created programmatically, every time the button is pressed it will create a new view that will replace the other, perform the animation and set the new view as the currentView for the next time the button is pressed.
class ViewController: UIViewController {
#IBOutlet weak var currentView: UIView!
#IBOutlet weak var container: UIView!
var flipped = false
#IBAction func buttonPressed(sender: AnyObject) {
let new: UIView!
if flipped {
new = UIImageView(frame: container.bounds)
new.backgroundColor = UIColor.redColor()
flipped = false
}
else {
new = UIButton(frame: container.bounds)
new.backgroundColor = UIColor.blueColor()
flipped = true
}
let options: UIViewAnimationOptions = [.TransitionFlipFromLeft, .AllowUserInteraction, .BeginFromCurrentState]
UIView.transitionFromView(currentView, toView: new, duration: 0.5, options: options, completion: nil)
self.currentView = new
}
}
Hope this helps :)
This piece of code works...
self.debug.hidden = true
let image = UIImage(data: data!)
self.debug.image = image
if (swipe.direction == UISwipeGestureRecognizerDirection.Left) {
UIView.transitionWithView(self.debug, duration: 1.0, options: [.TransitionFlipFromRight], animations: {
self.debug.hidden = false
}, completion: { _ in })
}
I load a new image into my image after I hide it.
Related
basically my current setup is like this
one storyboard ViewController with 3 types of UI View(container, front view, back view) inside of it.
what i want to accomplish (and i don't know how to implement #2)
user enters the data on the form(front of the card- View Controller number 1)
clicks the save button (do animation flipping and redirect to a new view controller)
the new view controller loads up (back of the card - View Controller number 2)
this is the current code flip example:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var goButton: UIButton!
#IBOutlet weak var optionsSegment: UISegmentedControl!
let owlImageView = UIImageView(image: UIImage(named:"img-owl"))
let catImageView = UIImageView(image: UIImage(named:"img-cat"))
var isReverseNeeded = false
override func viewDidLoad() {
super.viewDidLoad()
title = "Transitions Test"
setupView()
}
fileprivate func setupView() {
let screen = UIScreen.main.bounds
goButton.layer.cornerRadius = 22
//container to hold the two UI views
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 250))
containerView.backgroundColor = UIColor(red: 6/255, green: 111/255, blue: 165/255, alpha: 1.0)
containerView.layer.borderColor = UIColor.white.cgColor
containerView.layer.borderWidth = 2
containerView.layer.cornerRadius = 20
containerView.center = CGPoint(x: screen.midX, y: screen.midY)
view.addSubview(containerView)
//front view
catImageView.frame.size = CGSize(width: 100, height: 100)
catImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
catImageView.layer.cornerRadius = 50
catImageView.clipsToBounds = true
//back view
owlImageView.frame.size = CGSize(width: 100, height: 100)
owlImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
owlImageView.layer.cornerRadius = 50
owlImageView.clipsToBounds = true
containerView.addSubview(owlImageView)
}
#IBAction func goButtonClickHandler(_ sender: Any) {
doTransition()
}
fileprivate func doTransition() {
let duration = 0.5
var option:UIViewAnimationOptions = .transitionCrossDissolve
switch optionsSegment.selectedSegmentIndex {
case 0: option = .transitionFlipFromLeft
case 1: option = .transitionFlipFromRight
case 2: option = .transitionCurlUp
case 3: option = .transitionCurlDown
case 4: option = .transitionCrossDissolve
case 5: option = .transitionFlipFromTop
case 6: option = .transitionFlipFromBottom
default:break
}
if isReverseNeeded {
UIView.transition(from: catImageView, to: owlImageView, duration: duration, options: option, completion: nil)
} else {
UIView.transition(from: owlImageView, to: catImageView, duration: duration, options: option, completion: nil)
}
isReverseNeeded = !isReverseNeeded
}
}
There are a few alternatives for transition between view controllers with a flipping animation:
You can define a segue in IB, configure that segue to do a horizontal flipping animation:
If you want to invoke that segue programmatically, give the segue a “Identifier” string in the attributes inspector and then you can perform it like so:
performSegue(withIdentifier: "SecondViewController", sender: self)
Alternatively, give the actual destination view controller’s scene a storyboard identifier, and the presenting view controller can just present the second view controller:
guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") else { return }
vc.modalTransitionStyle = .flipHorizontal
vc.modalPresentationStyle = .currentContext
show(vc, sender: self)
If this standard flipping animation isn’t quite what you want, you can customize it to your heart’s content. iOS gives us rich control over custom transitions between view controller by specifying transitioning delegate, supplying an animation controller, etc. It’s a little complicated, but it’s outlined in WWDC 2017 Advances in UIKit Animations and Transitions: Custom View Controller Transitions (about 23:06 into the video) and WWDC 2013 Custom Transitions Using View Controllers.
I would like to present a UIViewController on top of the current view controller and set it's height to ~80% of the screen size. I've got the first part:
let additionalVC = ChartsViewController(currentSelection)
additionalVC = .overCurrentContext
present(additionalVC, animated: true)
I tried setting the self.view.frame inside my ChartsVC in viewDidLoad and couple of different things but it is always presented in the full screen mode.
That's what I want to achieve:
blueVC - currentVC
redVC - ChartsVC - VC on top of the current VC with ~80% of the original height
btw I'm doing everything programmatically, no xib and UIStoryboard.
There's a number of ways to achieve this.
You could use a 3rd party framework (http://transitiontreasury.com/) or the way I would do this.
Present the newVC where a transition = model over current context
ensure the newVC.views background color is clear
add another view where origin.y is the distance between the top and the desired gap. This is the view where all your objects will sit on.
If you need a coding example let me know, but its a pretty simple solution and looking at your code your 80% there.
Thomas
Implement a custom UIPresentationController. To use a custom view size, you only need to override a single property.
This code will simply inset the presented view controller by 50x100 pts:
class MyPresentationController: UIPresentationController {
// Inset by 50 x 100
override var frameOfPresentedViewInContainerView: CGRect {
return self.presentingViewController.view.bounds.insetBy(dx: 50, dy: 100)
}
}
To darken the presenting view controller, override presentationTransitionWillBegin() and dismissalTransitionWillBegin() to insert a shading view and animate it into view:
class MyPresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
return self.presentingViewController.view.bounds.insetBy(dx: 50, dy: 100)
}
let shadeView = UIView()
override func presentationTransitionWillBegin() {
self.shadeView.backgroundColor = UIColor.black
self.shadeView.alpha = 0
// Insert the shade view above the presenting view controller
self.shadeView.frame = self.presentingViewController.view.frame
self.containerView?.insertSubview(shadeView,
aboveSubview: self.presentingViewController.view)
// Animate it into view
self.presentingViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
self.shadeView.alpha = 0.3
}, completion: nil)
}
override func dismissalTransitionWillBegin() {
self.presentingViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
self.shadeView.alpha = 0.0
}, completion: nil)
}
}
To use your custom presentation controller, set the modalPresentationStyle and transitioningDelegate:
class MyViewController : UIViewController, UIViewControllerTransitioningDelegate {
//
// Your code
//
func presentCharts() {
let additionalVC = ChartsViewController(currentSelection)
additionalVC.modalPresentationStyle = .custom
additionalVC.transitioningDelegate = self
self.present(additionalVC, animated: true)
}
//
// UIViewControllerTransitioningDelegate protocol
//
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController? {
return MyPresentationController(presentedViewController: presented,
presenting: presenting)
}
}
In IOS 13 and Xcode 11, you can present ViewController with modalPresentationStyle = .automatic
Take Two ViewController.First view controller have a button and the button action name is clicked.The target is to clicking the button we want to add secondVC as a child of first view controller and show secondVC 80% of the first view controller.again click button we remove secondVC from first view controller. below is the code for click button action.
#IBAction func clicked(_ sender: UIButton) {
if !isshown{
isshown = true
self.addChildViewController(vc)
self.view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
let height = view.frame.height
let width = view.frame.width
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.vc.view.frame = CGRect(x: 0, y: 100 , width: width, height: height - 100)
}, completion: { (result) in
// do what you want to do
})
}else{
isshown = false
UIView.animate(withDuration: 0.3,
delay: 0,
options: UIViewAnimationOptions.curveEaseIn,
animations: { () -> Void in
var frame = self.vc.view.frame
frame.origin.y = UIScreen.main.bounds.maxY
self.vc.view.frame = frame
}, completion: { (finished) -> Void in
self.vc.view.removeFromSuperview()
self.vc.removeFromParentViewController()
})
}
}
here vc is a reference of secondVC.
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Second") as! secondVC
change below piece of code to get whatever percentage you want.
self.vc.view.frame = CGRect(x: 0, y: 100 , width: width, height: height - 100)
I have seen similar questions on here before, but they relate to just simple buttons, not bar button items. Keep in mind- bar button items and regular buttons have different properties, thus the answers on the other posts will not work in this situation.
Is there a way, in swift, that I can make my refresh bar button item spin 360 degrees one time (to show that an action occurred)?
In order to animate a UIBarButtonItem, we need to assign it a customView and animate that instead.
What I've done below is create an ordinary UIButton, and I've assigned it's image to a refresh icon named "refresh". Next, I set my UIBarButtonItem's customView to be my new button. Now that barButton has a customView, I can call my rotateBarButton() method to perform the animation code.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var barButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRectMake(0, 0, 40, 40)) // Create new button & set its frame
button.setImage(UIImage(named: "refresh"), forState: .Normal) // Assign an image
barButton.customView = button // Set as barButton's customView
}
func rotateBarButton() {
// Gets you half way there //
UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.barButton.customView?.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))
}, completion: nil)
// Rotates all the way around //
UIView.animateWithDuration(0.5, delay: 0.5, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.barButton.customView?.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 2))
}, completion: nil)
}
}
I am trying to Flip two UIViews. I've try to flip UIView using programmatically and it works perfect. But when i've try to flip UIView that i created in storyboard it not works, First time it flip UIView but second time it flip blank UiViews? Any one have any idea is there any mistake in my code?
In this picture Top left Debug view Hierarchy picture is before animating button and bottom left Debug view Hierarchy picture is after animating picture.
When second time i animate the UIView it Flip like this below picture.
class ViewController: UIViewController {
#IBOutlet var container: UIView!
#IBOutlet var blueSquare : UIView!
#IBOutlet var redSquare : UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func animateButtonTapped(sender: AnyObject) {
// create a 'tuple' (a pair or more of objects assigned to a single variable)
var views : (frontView: UIView, backView: UIView)
if ((self.redSquare.superview) != nil) {
views = (frontView: self.redSquare, backView: self.blueSquare)
}
else {
views = (frontView: self.blueSquare, backView: self.redSquare)
}
// set a transition style
let transitionOptions = UIViewAnimationOptions.TransitionFlipFromLeft
// with no animation block, and a completion block set to 'nil' this makes a single line of code
UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil)
}
}
Programmatically
That code is perfectly works.
let container = UIView()
let redSquare = UIView()
let blueSquare = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// set container frame and add to the screen
self.container.frame = CGRect(x: 60, y: 60, width: 200, height: 200)
self.view.addSubview(container)
// set red square frame up
// we want the blue square to have the same position as redSquare
// so lets just reuse blueSquare.frame
self.redSquare.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
self.blueSquare.frame = redSquare.frame
// set background colors
self.redSquare.backgroundColor = UIColor.redColor()
self.blueSquare.backgroundColor = UIColor.blueColor()
// for now just add the redSquare
// we'll add blueSquare as part of the transition animation
self.container.addSubview(self.redSquare)
}
#IBAction func animateButtonTapped(sender: AnyObject) {
// create a 'tuple' (a pair or more of objects assigned to a single variable)
var views : (frontView: UIView, backView: UIView)
if((self.redSquare.superview) != nil){
views = (frontView: self.redSquare, backView: self.blueSquare)
}
else {
views = (frontView: self.blueSquare, backView: self.redSquare)
}
// set a transition style
let transitionOptions = UIViewAnimationOptions.TransitionFlipFromLeft
// with no animation block, and a completion block set to 'nil' this makes a single line of code
UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil)
}
UPDATE
var check = true
#IBAction func animateButtonTapped(sender: AnyObject) {
// create a 'tuple' (a pair or more of objects assigned to a single variable)
var views : (frontView: UIView, backView: UIView)
if (check == true) {
views = (frontView: self.redSquare, backView: self.blueSquare)
check = false
}
else {
views = (frontView: self.blueSquare, backView: self.redSquare)
check = true
}
// set a transition style
let transitionOptions : UIViewAnimationOptions = [UIViewAnimationOptions.TransitionFlipFromLeft, UIViewAnimationOptions.ShowHideTransitionViews]
// with no animation block, and a completion block set to 'nil' this makes a single line of code
UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil)
}
The default behaviour for transitionFromView removes the view after animation.
let transitionOptions: UIViewAnimationOptions = [.TransitionFlipFromLeft, .ShowHideTransitionViews]
From the documentation: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/swift/struct/c:#E#UIViewAnimationOptions
ShowHideTransitionViews
When present, this key causes views to be hidden or shown (instead of removed or added) when performing a view transition. Both views must already be present in the parent view’s hierarchy when using this key. If this key is not present, the to-view in a transition is added to, and the from-view is removed from, the parent view’s list of subviews.
Ah in storyboard I see that you add both views to the container but in code you only add the redsquare. Perhaps remove the bluesquare from being within the container in storyboard?
I am trying to animate the root-view-controller-change in my app. After I swap the view controllers, I load the data necessary for the 2nd controller right away. While the data is loading, I show a loader(MBProgressHUD). This is my function for swapping the view controllers:
class ViewUtils {
class func animateRootViewController(duration: NSTimeInterval, changeToViewController: UIViewController) {
let window = UIApplication.sharedApplication().delegate?.window?
if window == nil {
return
}
UIView.transitionWithView(window!,
duration: duration,
options: UIViewAnimationOptions.TransitionFlipFromLeft | UIViewAnimationOptions.AllowAnimatedContent,
animations: {
window!.rootViewController = changeToViewController
},
completion: nil
)
}
}
All good with this but one thing - it totally breaks the loader. I am attaching an imagine of what's happening:
This is the 2nd view controller while rotating. Once the rotation is complete, the loader appears just fine, both the spinner and the text tween to the correct position in the rounded rectangle.
I really don't understand why this happens, would somebody explain it to me, please? Is there a way to prevent it?
The code of the 2nd view controller where I show the loader:
override func viewDidLoad() {
super.viewDidLoad()
hud = HUD(containingView: view)
hud.show()
createBackground()
}
And my hud class:
class HUD {
private var hudBG: UIView!
private var view: UIView!
private(set) var isShown = false
init(containingView: UIView) {
view = containingView
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func show() {
if !isShown {
if(hudBG == nil) {
hudBG = UIView(frame: CGRectMake(0, 0, view.bounds.width, view.bounds.height))
hudBG.backgroundColor = UIColor(white: 0, alpha: 0.4)
}
view.addSubview(hudBG)
let hud = MBProgressHUD.showHUDAddedTo(view, animated: true)
hud.mode = MBProgressHUDModeIndeterminate
hud.labelText = "Cargando"
hudBG.alpha = 0
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.hudBG.alpha = 1
})
isShown = true
}
}
func hide() {
if isShown {
UIView.animateWithDuration(0.3, animations: {
() -> Void in
self.hudBG.alpha = 0
}, completion: {
(b) -> Void in
self.hudBG.removeFromSuperview()
})
MBProgressHUD.hideHUDForView(view, animated: true)
isShown = false
}
}
}
Thanks a lot for any ideas!
You are adding the hud to a view that is not properly initialized yet.
If you are loading the view controller from a xib or storyboard, the view and it's subviews have the size as they were loaded from interface.
You have to add the hud after the views have been resized to their final size.
If you move
hud = HUD(containingView: view)
hud.show()
to viewDidLayoutSubviews, it should work fine.
I noticed a similar problem when moving an app from iOS 7 to iOS 8. During animations, especially when scaling was involved, the view positions got distorted.
I am pretty sure it's a bug. The simplest workaround is to animate only screenshots or view snapshots, not actual views - it's more work and you can't have views animating when the main animation is in progress but in general it's a more stable solution.