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?
Related
I'm working on a game prototype in Swift using UIKit and SpriteKit. The inventory (at the bottom of this screenshot) is a UIView with UIImageView subviews for the individual items. In this example, a single acorn.
I have the acorn recognizing the "pan" gesture so I can drag it around. However, it renders it below the other views in the hierarchy. I want it to pop out of the inventory and be on top of everything (even above its parent view) so I can drop it onto other views elsewhere in the game.
This is what I have as my panHandler on the acorn view:
#objc func panHandler(gesture: UIPanGestureRecognizer){
switch (gesture.state) {
case .began:
removeFromSuperview()
controller.view.addSubview(self)
case .changed:
let translation = gesture.translation(in: controller.view)
if let v = gesture.view {
v.center = CGPoint(x: v.center.x + translation.x, y: v.center.y + translation.y)
}
gesture.setTranslation(CGPoint.zero, in: controller.view)
default:
return
}
}
The problem is in the .began case, when I remove it from the superview, the pan gesture immediately cancels. Is it possible to remove the view from a superview and add it as a subview elsewhere while maintaining the pan gesture?
Or, if my approach is completely wrong, could you give me pointers how to accomplish my goal with another method?
The small answer is you can keep the gesture working if you don't call removeFromSuperview() on your view and add it as a subview right away to your controller view, but that's not the right way to do this because if the user cancels the drag you will have to re add to your main view again and if that view your dragging is heavy somehow it gets laggy and messy quickly
The long answer, and in my opinion is the right way to do it and what apple actually does in all drag and drop apis is
you can actually make a snapshot of the view you want to drag and add it as a subview of the controller view that's holding all your views and then call bringSubviewToFront(_ view: UIView) to make sure it's the top most view in the hierarchy and pass in the snapshot you took of the dragging view
in the .began you can hide the original view and in the .ended you can show it again
and also on .ended you can either take that snapshot and add to the dropping view or do anything else with it's dropping coordinates
I made a sample project to apply this
Here is the storyboard design and view hierarchy
Here is the ViewController code
class ViewController: UIViewController {
#IBOutlet var topView: UIView!
#IBOutlet var bottomView: UIView!
#IBOutlet var smallView: UIView!
var snapshotView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(panning(_:)))
smallView.addGestureRecognizer(pan)
}
#objc private func panning(_ pan: UIPanGestureRecognizer) {
switch pan.state {
case .began:
smallView.backgroundColor = .systemYellow
snapshotView = smallView.snapshotView(afterScreenUpdates: true)
smallView.backgroundColor = .white
if let snapshotView = self.snapshotView {
self.snapshotView = snapshotView
view.addSubview(snapshotView)
view.bringSubviewToFront(snapshotView)
snapshotView.backgroundColor = .blue
snapshotView.center = bottomView.convert(smallView.center, to: view)
}
case .changed:
guard let snapshotView = snapshotView else {
fallthrough
}
smallView.alpha = 0
let translation = pan.translation(in: view)
snapshotView.center = CGPoint(x: snapshotView.center.x + translation.x, y: snapshotView.center.y + translation.y)
pan.setTranslation(.zero, in: view)
case .ended:
if let snapshotView = snapshotView {
let frame = view.convert(snapshotView.frame, to: topView)
if topView.frame.contains(frame) {
topView.addSubview(snapshotView)
snapshotView.frame = frame
smallView.alpha = 1
} else {
bottomView.addSubview(snapshotView)
let newFrame = view.convert(snapshotView.frame, to: bottomView)
snapshotView.frame = newFrame
UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) {
snapshotView.frame = self.smallView.frame
} completion: { _ in
self.snapshotView?.removeFromSuperview()
self.snapshotView = nil
self.smallView.alpha = 1
}
}
}
default: break
}
}
}
Here is how it ended up
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 just wanted to create a view and when it shown then the whole background will be dimmed like an alert view controller. If it is possible then please guide me and if possible then provide me code.
Thank you
The simplest way for doing that is to add a semi-transparent background (e.g. black with alpha less than 1.0) view, which contains the alert view. The background view should cover all other views in the view controller.
You can also use a modal view controller which has such a background view as its view, and presenting this controller with presentation style Over Full Screen.
// Here is the wrapper code i use in most of my project now a days
protocol TransparentBackgroundProtocol {
associatedtype ContainedView
var containedNib: ContainedView? { get set }
}
extension TransparentBackgroundProtocol where ContainedView: UIView {
func dismiss() {
containedNib?.superview?.removeFromSuperview()
containedNib?.removeFromSuperview()
}
mutating func add(withFrame frame: CGRect, toView view: UIView, backGroundViewAlpha: CGFloat) {
containedNib?.frame = frame
let backgroundView = configureABlackBackGroundView(alpha: backGroundViewAlpha)
view.addSubview(backgroundView)
guard let containedNib = containedNib else {
print("No ContainedNib")
return
}
backgroundView.addSubview(containedNib)
}
private func configureABlackBackGroundView(alpha: CGFloat) -> UIView {
let blackBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
blackBackgroundView.backgroundColor = UIColor.black.withAlphaComponent(alpha)
return blackBackgroundView
}
}
// Sample View shown like alertView
class LogoutPopUpView: UIView, TransparentBackgroundProtocol {
// MARK: Variables
weak var containedNib: LogoutPopUpView?
typealias ContainedView = LogoutPopUpView
// MARK: Outlets
// MARK: Functions
class func initiate() -> LogoutPopUpView {
guard let nibView = Bundle.main.loadNibNamed("LogoutPopUpView", owner: self, options: nil)?[0] as? LogoutPopUpView else {
fatalError("Cann't able to load nib file.")
}
return nibView
}
}
// where u want to show pop Up
logOutPopup = LogoutPopUpView.instanciateFromNib()
let view = UIApplication.shared.keyWindow?.rootViewController?.view {
logOutPopup?.add(withFrame: CGRect(x: 30, y:(UIScreen.main.bounds.size.height-340)/2, width: UIScreen.main.bounds.size.width - 60, height: 300), toView: view, backGroundViewAlpha: 0.8)
}
// for dismiss
self.logOutPopup?.dismiss()
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.
i want to create a slide out menu which has a normal width of about 50 pixels, and if the user press the expand button i will also show the labels for the button.
Like in this example:
What is the correct way to create such a menu? I though about using 2 views and set the size of contentview to width-50pixels.
But i am unable to change the frame of my UIView in the ViewDidLoad function. (this is an example)
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame = CGRect(x:50, y:0, width:974, height:768)
sidebarIsOpen = false
}
And if the user click on the expand Button
#IBAction func expandButtonClicked(sender : AnyObject) {
var x = self.sidebarIsOpen! ? 50 : 300
UIView.animateWithDuration(0.2, animations: {
self.view.frame = CGRect(x:x, y:0, width:300, height:768)
}, completion: { _ in
self.sidebarIsOpen = !(self.sidebarIsOpen!)
})
}
If i click the button again, everything is fine. But on ViewDidLoad i am unable to move the contentview to right.
Thanks in advance
Ill found already the solution by myself.
Created 2 views and animate the "contentview" on button click.
#IBOutlet weak var menuView: UIView!
#IBAction func toggleMenuButton(sender: UIBarButtonItem) {
var x = self.sidebarIsOpen! ? 50 : 200
UIView.animateWithDuration(0.2, animations: {
self.contentView.frame = CGRect(x:x, y:0, width:974, height:768)
}, completion: { _ in
self.sidebarIsOpen = !(self.sidebarIsOpen!)
})
}