Making an animation to slide and hide/show an UIView(swift 5) - ios

I want to add animation to these two views.
Red UIView
Green UIView
My storyboard look like this
From the picture I want to add an animation when click on these two views.
First start with hide red UIView.
Action : 1
when i click on green view i want green uiview silde to the right side until it disappear
and the red UIView will slide out from the right side immediately.
red uiview slide from right side
and stopp when it is at that point in the storyboard and hide green UIView.
Action : 2
and when i click on red view i want it to slide right until it disappears. Show green UIView and comes out from the right corner as well and hide red UIView.
red UIView slide out
My Code
import UIKit
class TestViewCell: UICollectionViewCell {
#IBOutlet weak var bgView: UIView!
#IBOutlet weak var bgAlertView: UIView!
#IBOutlet weak var imgAlert: UIImageView!
#IBOutlet weak var bgAlreadyAlertView: UIView!
#IBOutlet weak var imgAlreadyAlert: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
//Make an action when tap on bgAlertView
let actionBgAlert : Selector = #selector(self.actionBgAlert)
let viewPostsViewGesture = UITapGestureRecognizer(target: self, action: actionBgAlert)
bgAlertView.isUserInteractionEnabled = true
bgAlertView.addGestureRecognizer(viewPostsViewGesture)
//Make an action when tap on bgAlreadyAlertView
let actionBgAlreadyAlert : Selector = #selector(self.actionBgAlreadyAlert)
let viewAlreadyViewGesture = UITapGestureRecognizer(target: self, action: actionBgAlreadyAlert)
bgAlreadyAlertView.isUserInteractionEnabled = true
bgAlreadyAlertView.addGestureRecognizer(viewAlreadyViewGesture)
}
//action1
#objc func actionBgAlert(sender: UITapGestureRecognizer){
if imgAlert.image == #imageLiteral(resourceName: "alarm") {
self.bgAlertView.isHidden = true
self.bgAlreadyAlertView.isHidden = false
}
//action2
#objc func actionBgAlreadyAlert(sender: UITapGestureRecognizer){
if imgAlreadyAlert.image == #imageLiteral(resourceName: "alarmedMain") {
self.bgAlertView.isHidden = false
self.bgAlreadyAlertView.isHidden = true
}
}

In the storyboard set constraints on the size of the views. Set constraints from the right side of the red view and green view from the right side of the superview they share. Define some constants for the values needed for both positions for both views.
Then something like this:
#IBOutlet weak var greenConstraint : NSLayoutConstraint
#IBOutlet weak var redConstraint : NSLayoutConstraint
let greenSlideOutValue = -2000.0 // big enough to get green view offscreen
let redSlideInValue = 0.0 // aligns red view right edge to superview
let greenSlideInValue = 100.0 // puts green view onscreen offset from right edge
let redSlideOutValue = -2500.0 // big enough to get red view offscreen.
UIView.animate(withDuration: 0.75, delay: 1.0, options: .curveEaseOut, animations: {
greenConstraint.constant = greenSlideOutValue
redConstraint.constant = redSlideInValue
self.view.layoutIfNeeded()
}, completion: { finished in
print(“slide green out, red in”)
})
Do this in the event handler for say a tap or gesture recognizer associated with the views. Do similar for the red view event(s)
Code isn’t compiled and is just typed in but should get you started.

Related

UIStackView show/hide animation is not working properly

I'm using UIStackView and it contains 3 UIView instances, which has fixed height
I'm trying to hide these subviews by clicking button
first and second view show/hide well with proper animation
but last view doesn't animate
class ViewController: UIViewController {
private var flag: Bool = true
#IBOutlet weak var targetView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonDidTapped(_ sender: Any) {
flag = !flag
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
self.targetView.isHidden = !self.flag
}
}
}
The issue is the way stack views change their frames when hiding an arranged subview.
Easiest way to see what's happening:
set your Green view to Alpha: 0.5
toggle .isHidden on the Blue view
You'll see that the 50% translucent Green view "slides up over" the Blue view... the Blue view does not "shrink in height" during the animation.
To solve your specific issue, set Clips To Bounds to true on your stack view. Now, when you toggle .isHidden on your Green view, the animation will look correct.
That will not change the "slide over" appearance if you have translucent views, but that's a different issue.
As a side note, you can simplify your code and get rid of the flag like this:
UIView.animate(withDuration: 0.5) {
// not needed
//self.view.layoutIfNeeded()
self.targetView.isHidden.toggle()
}
Try change your code from:
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
self.targetView.isHidden = !self.flag
}
to:
self.targetView.isHidden = !self.flag
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Looks like you animate before change.

UIView.transition animation causing view to become nil

Goal of code is that on each tap of a view containing 2 UIImageview, to have the bottom image go on top of the to image, and so on each time I tap.
I have a view container with 2 UIImageview on top of each other:
#IBOutlet weak var imagesContainer: UIView!
#IBOutlet weak var imageZero: UIImageView!
#IBOutlet weak var imageOne: UIImageView!
I add a tap gesture in ViewDidLoad:
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImagesContainer(_:)))
self.imagesContainer.addGestureRecognizer(tapGestureRecognizer
Also a top level variable, to control which image to bring on top, and which to push down:
var imageOnTopIsImageZero = true
so that when I tap on the container, the under image comes on top.
While developing I implemented a transition without animation:
#objc func didTapImagesContainer(_ sender: UITapGestureRecognizer) {
let imageToBringOnTop: UIImageView? = imageOnTopIsImageZero ? self.imageOne : self.imageZero
let ImagetoBringDown: UIImageView? = imageOnTopIsImageZero ? self.imageZero : self.imageOne
imageToBringOnTop?.layer.zPosition = 1.0
ImagetoBringDown?.layer.zPosition = 0.0
self.imageOnTopIsImageZero.toggle()
}
This works fine. Now I tried to implement the same transition with animation:
#objc func didTapImagesContainer(_ sender: UITapGestureRecognizer) {
let imageToBringOnTop: UIImageView? = imageOnTopIsImageZero ? self.imageOne : self.imageZero
let ImagetoBringDown: UIImageView? = imageOnTopIsImageZero ? self.imageZero : self.imageOne
UIView.transition(from: ImagetoBringDown!, to: imageToBringOnTop!, duration: 1.0, options: .transitionCrossDissolve, completion: nil)
self.imageOnTopIsImageZero.toggle()
}
The first time I tap on the image, the transition happens correctly, underimage is cross disolved into the on top image.
But on the second tap, imageToBringOnTop is nil!
I really don't understand why the animation has an effect on the view content. What is the cause, and how to resolve this issue?
The docs say:
fromView
The starting view for the transition. By default, this view is removed from its superview as part of the transition.
Detail:
The idea of a transition animation is that you're changing the view hierarchy by replacing one view with another in an animated way. If you are not doing such a 'transition', you can use other (non-transition) animation API like animateWithDuration:delay:options:animations:completion:
Or there is support for keeping two views in the hierarchy, one shown, one hidden, using the transition animation API, if you include UIViewAnimationOptionShowHideTransitionViews in the option set:
UIView.transition(from: ImagetoBringDown!, to: imageToBringOnTop!,
duration: 1.0,
options: [.transitionCrossDissolve, .showHideTransitionViews],
completion: nil)
So "showHideTransitionViews" means 'show/hide the from/to instead of add/remove'
Rather than setting the z positions, transition actually removes the from view from the view hierarchy, and adds the to view to the view hierarchy (documentation):
Parameters
fromView
The starting view for the transition. By default, this view is
removed from its superview as part of the transition.
toView The ending view for the transition. By default, this view is
added to the superview of fromView as part of the transition.
Also note that since your VC is holding a weak reference to the images, their superviews are the only objects holding a strong reference to them. Once one of them is removed from its superview, your VC's weak reference becomes nil.
To fix this, simply use strong references, remove the word weak:
#IBOutlet var imageZero: UIImageView!
#IBOutlet var imageOne: UIImageView!

Setting origin of container to another container trouble (Swift 4.2)

I'm trying to set the origin and width/height of one UIView (red) to a second UIView (blue).
I am calling UIView.frame.origin or size and for some reason the y origin doesn't work.
I've also tried with layout constraints (see it commented out below), but this is overriding my blue fully constrained view.
Then I have a button that animates the red view to the side so you can see the blue view underneath, but I can't get them to line up to start with. Below is my code. In interface builder, I have both UIViews set up as containers. Blue is fully constrained with auto layout and red has no constraints.
import UIKit
class ViewController: UIViewController
{
#IBOutlet weak var blueContainer: UIView!
#IBOutlet weak var redContainer: UIView!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
print(redContainer.frame)
redContainer.frame.origin.x = view.frame.width/2
redContainer.frame.size.width = view.frame.width
//try to line up y with origin and size
redContainer.frame.origin.y = blueContainer.frame.origin.y
redContainer.frame.size.height = blueContainer.frame.size.height
//also tried by using constraints
//redContainer.topAnchor.constraint(equalTo: blueContainer.topAnchor).isActive = true
//redContainer.heightAnchor.constraint(equalTo: blueContainer.heightAnchor).isActive = true
print(redContainer.frame)
}
#IBAction func slideRed(_ sender: Any) {
if redContainer.frame.origin.x == 0 {
UIView.animate(withDuration: 0.5) {
self.redContainer.frame.origin.x = self.view.frame.width/2
}
button.setTitle("Come Back Red!", for: .normal)
} else {
UIView.animate(withDuration: 0.5) {
self.redContainer.frame.origin.x = 0
}
button.setTitle("Go Away Red!", for: .normal)
}
}
}
ViewDidLoad does not guarantee the view has laid out its constraints. So when blueContainer's frame and size is zero, you will not see any effect on redContainer. You should use viewDidLayoutSubviews to get the correct frame and size from blueContainer.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
redContainer.frame.origin.x = view.frame.width/2
redContainer.frame.size.width = view.frame.width
//try to line up y with origin and size
redContainer.frame.origin.y = blueContainer.frame.origin.y
redContainer.frame.size.height = blueContainer.frame.size.height
}

How to rotate iOS label and make it stick to edge of screen?

I've got the following structure for example:
I want to rotate my label by 270degrees to achieve this:
via CGAffineTransform.rotated next way:
credentialsView.text = "Developed in EVNE Developers"
credentialsView.transform = credentialsView.transform.rotated(by: CGFloat(Double.pi / 2 * 3))
but instead of expected result i've got the following:
So, what is the correct way to rotate view without changing it's bounds to square or whatever it does, and keep leading 16px from edge of screen ?
I tried a lot of ways, including extending of UILabel to see rotation directly in storyboard, putted dat view in stackview with leading and it also doesn't helps, and etc.
Here is the solution which will rotate your label in an appropriate way forth and back to vertical-horizontal state. Before running the code, set constraints for your label in storyboard: leading to 16 and vertically centered.
Now check it out:
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
// Your leading constraint from storyboard, initially set to 16
#IBOutlet weak var leadingConstraint: NSLayoutConstraint!
var isHorizontal: Bool = true
var defaultLeftInset: CGFloat = 16.0
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
label.text = "This is my label"
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapAction)))
}
#objc func tapAction() {
if self.isHorizontal {
// Here goes some magic
// constraints do not depend on transform matrix,
// so we have to adjust a leading one to fit our requirements
leadingConstraint.constant = defaultLeftInset - label.frame.width/2 + label.frame.height/2
self.label.transform = CGAffineTransform(rotationAngle: .pi/2*3)
}
else {
leadingConstraint.constant = defaultLeftInset
self.label.transform = .identity
}
self.isHorizontal = !self.isHorizontal
}
}

How to stack custom views inside a UIStackView

Note: I'm pretty new working with iOS UI.
I want to create a custom view that stacks a custom view inside.
So I created the custom UIStackView
class CustomStackView: UIStackView {
func addItem(color:UIColor){
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "RowView", bundle: bundle)
let rowView = RowView();
let view = nib.instantiate(withOwner: rowView, options: nil).first as! UIView
rowView.addSubview(view)
rowView.view.backgroundColor = color;
addArrangedSubview(rowView)
}
}
class RowView :UIView{
#IBOutlet var view: UIView!
override public var intrinsicContentSize: CGSize {
return CGSize(width: view.frame.width,height:view.frame.height)
}
}
in the RowView.xib I created a simple layout for testing:
Simulated Metrics = Freeform
Height = 100
And the ViewController.swift:
class ViewController: UIViewController {
#IBOutlet weak var customStackView: CustomStackView!
#IBOutlet weak var constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
customStackView.addItem(color: UIColor.red)
customStackView.addItem(color: UIColor.blue)
customStackView.addItem(color: UIColor.green)
}
#IBAction func click(_ sender: Any) {
constraint.constant = -customStackView.frame.height
UIView.animate(withDuration: 4, animations: {
self.view.layoutIfNeeded();
},completion:nil)
}
}
The result:
The first and second item are displayed correctly but the third is higher than expected.
In addition if I click the button (which should hide the Stackview) keep the "extra" height visible:
How can I fix that?
Edit: Tried the #KristijanDelivuk solution adding a trailing view. And didn't work. Adding cyan color to the view I got this result:
override func viewDidLoad() {
super.viewDidLoad()
customStackView.addItem(color: UIColor.red)
customStackView.addItem(color: UIColor.blue)
customStackView.addItem(color: UIColor.green)
let view = UIView();
view.heightAnchor.constraint(equalToConstant: 50).isActive = true;
view.backgroundColor = UIColor.cyan;
customStackView.addArrangedSubview(view)
}
You can try adding an empty UIView as your last element of UIStackView:
So your hierarchy should look something like this:
- STACKVIEW
-- 1ST ADDED CUSTOM VIEW
-- 2ND ADDED CUSTOM VIEW
-- 3RD ADDED CUSTOM VIEW
-- EMPTY UIVIEW
Empty UIView will take all unallocated space from 3rd view and all should be displayed correctly.
For repositioning button after hiding/showing stackview you can create for example "top constraint" and then on tap change top constraint height to (-) stackview.height or (+) stackview.height - This shouldn't be any problem.

Resources