IBOutlet is nil after flip transition animation - ios

My two IBoutlet of two views are there which i am trying to flip. After UIView.transition, "from" IBOutlet sets to nil, unable to figure out the reason behind this.
#IBOutlet weak var showingSideView: shadowView!
#IBOutlet weak var hiddenSideView: shadowView!
UIView.transition(from: showingSideView, to: hiddenSideView, duration: 0.7, options: transitionDirection) { (isFlipped) in
self.showingBack = !self.showingBack
}

That is because your view in "from" will be removed from its superview after the transition finishes.
Read more in the docs
As you defined your outlet as weak, there is no retaining object anymore so it gets deallocated.

If you read through the documentation :
By default, the view in fromView is replaced in the view hierarchy by the view in toView. If both views are already part of your view hierarchy, you can include the showHideTransitionViews option in the options parameter to simply hide or show them.
So, to keep your views in the hierarchy, add that option:
#IBOutlet var showingSideView: shadowView!
#IBOutlet var hiddenSideView: shadowView!
UIView.transition(from: showingSideView,
to: hiddenSideView,
duration: 0.7,
options: [.transitionFlipFromLeft, .showHideTransitionViews])
{ (isFlipped) in
self.showingBack = !self.showingBack
}
Edit:
Just for clarification...
Apple now recommends NOT using weak for #IBOutlets, but that has no effect on the problem here. When using .showHideTransitionViews the reference will not become nil even if the #IBOutlets are set to weak.

Related

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!

View Controller loses connection to constraints in IBOutlets when switching tabs in UITabView

OK, so here's the relevant bit of my code:
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var questionView: QuestionView!
#IBOutlet weak var answerView: AnswerView!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var tableViewTopConstraint: NSLayoutConstraint! // Active at start, attaches tableView.topAnchor to questionView.bottomAnchor
#IBOutlet weak var tableViewTopAlternateConstraint: NSLayoutConstraint! // Inactive at start, attaches tableView.topAnchor to answerView.bottomAnchor
func submitAnswer() {
tableViewTopConstraint.isActive = false
tableViewTopAlternateConstraint = true
}
func newQuestion() {
tableViewTopConstraint.isActive = true
tableViewTopAlternateConstraint = false
}
}
I'm building a question/answer type of app and the two subviews are different sizes for design reasons. I have it set so that it toggles between the two subviews depending on the current state (toggling back for a new question when the user taps to advance), and it works fine...
...until I go to a different tab on the UITabView that encloses everything (e.g. to change the settings then resume testing)
The moment the tab changes it's like the NSLayoutConstraint outlets no longer exist. The orders to change them still process (I've verified this in the console), but they do nothing.
I've tried declaring the outlets as strong (there was no difference in behavior)
I've removed the outlets and managed them in code (it worked the first time, but afterward it stretched the shorter QuestionView's height to match tableView's new top position instead of moving tableView up to the bottom of QuestionView as was intended)
I've tried implementing a UITabViewController so that I could call tabBar(_:didSelect:) and replace the view controller with a brand new instance of MyViewController() whenever I switch back to that tab (It loads the first time, but when I try to switch tabs it finds nil when accessing any of the IBOutlets...even though I didn't tap on the tab for the testing view and filtered by item.tag when replacing the existing view controller)
Any suggestions on how else to attack this problem?
First, don't set #IBOutlet properties to weak. I know that's the default, but it's not correct.
If you have this:
#IBOutlet weak var tableViewTopAlternateConstraint: NSLayoutConstraint!
connected to a constraint via Storyboard, and you do this:
tableViewTopAlternateConstraint.isActive = false
you just removed that constraint from existence.
In addition, if you have two different Top constraints in Storyboard, you should be getting an Error indicator as they cannot be satisfied.
Better to either change the Priorities, or use a single constraint and change the .constant value.
So, two constraints in Storyboard:
set the "default" constraint to Priority: High (750)
set the "alternate" constraint to PriorityL Low (250)
and your code becomes:
func submitAnswer() {
tableViewTopConstraint.priority = .defaultLow
tableViewTopAlternateConstraint.priority = .defaultHigh
}
func newQuestion() {
tableViewTopAlternateConstraint.priority = .defaultLow
tableViewTopConstraint.priority = .defaultHigh
}

swift bringSubViewToFront

I have a bottomView with opacity set to 0.65, and embedded in that View I have 5 buttons - which also gets the 0.65 opacity attribute - but How do I make the buttons get rid of the opacity?
I want the buttons to be very clear
I have tried to make outlets of the View and the Buttons and set the buttons to the front - view, but it doesn't change the appearance of the button??
#IBOutlet weak var bottomView: UIView!
#IBOutlet weak var findVejOutlet: UIButton!
#IBOutlet var superViewOutlet: UIView!
#IBAction func findVejButton(_ sender: Any) {
superViewOutlet.bringSubview(toFront: findVejOutlet)
}
override func viewDidLoad() {
super.viewDidLoad()
settingView()
}
func settingView(){
bottomView.bringSubview(toFront: findVejOutlet)
}
If you set the opacity of a view to a value of less than 1, it makes all the contents of a view partly transparent (including subviews). You can't change that, and opacity has nothing to do with the front-to-back ordering of the views.
You either need to make the parent view fully opaque and the non-button subviews partly transparent, or remove the buttons from the translucent view and instead put them in the common parent view.

Can't animate a position change of two container UIViews that have child view controllers

I have two views in self.view that are container views (via storyboard: each has an embed segue in viewDidLoad which loads up their respective child view controller). They're called self.leftPane and self.rightPane. I'm using auto-layout.
I want to animate a position change of self.leftPane, as it moves from x=0 to x=(negative something). I'm basically trying to make a hamburger menu on the left, in a simple fashion...
#IBOutlet weak var leftPane: UIView! // has child view controller embedded
#IBOutlet weak var rightPane: UIView! // has child view controller embedded
// in/after viewDidAppear
self.leftPaneLeftConstraint.constant = 0 - self.leftPane.frame.size.width + 20.0
UIView.transition(with: self.leftPane, duration: 1.0, options: [], animations: {
self.leftPane.layoutIfNeeded()
}, completion: { (finished) in
self.leftPaneIsFull = false
})
The problem is that there's no animation -- it just jumps. I've tried UIView.transition with different views, and different UIView.animate flavors, all with the same effect (no actual animation).
What am I missing? Thanks!
You should use UIView.animate instead of UIView.transition.I wrote down the code sample.Are you try this code?
#IBOutlet weak var leftPane: UIView! // has child view controller
embedded
#IBOutlet weak var rightPane: UIView! // has child view controller embedded
// in/after viewDidAppear
UIView.animate(withDuration: 1.0, animations: {
self.leftPaneLeftConstraint.constant = 0 - self.leftPane.frame.size.width + 20.0
self.view.layoutIfNeeded()
}, completion: { (success) -> Void in
//You can write something you want
})
}
})
Enjoy it :)

Removing a UIView and bringing back the UIView in swift

I am using a UIView not a view controller/ I am removing the view once the delete button is clicked. I am trying to make the view come back when a user wants the view back.
example : myView.removeFromSuperview()
Is there anyway to bring the view back?
In swift thank you guys!
#IBOutlet weak var brokeView: UIView!
#IBOutlet weak var deleteButton: UIButton!
#IBAction func deleteViewButton(sender: AnyObject) {
if brokeView != nil {
brokeView.removeFromSuperview()
}
#IBOutlet weak var brokeView: UIView!
you already have a reference to the view brokeView which you are going to remove and add again, but its weak so it will be deallocated once it is removed from superView. Since you need this brokeView to add back make it strong.
#IBOutlet var brokeView: UIView!
Now you can add it back like
view.addSubview(brokeView)
No, there is no way to get it back unless you initialise it again. You can hide the view and unhide it.
viewToBeHidden.hidden = true
//when you want to make it reappear unhide it.
viewToBeHidden.hidden = false
Or like #rmaddy suggested keep the reference.
var myView = UIView()
myView.removeFromSuperview
//then just add it back.
self.view.addSubView(myView)
if you removing it from superview, you can add it again, but you should not make that view as nil other wise you will have to create new one.Removing from superview only remove view from parent view.
subview.removeFromSuperview() // remove from parent view
parentView.addSubview(subview) //adding again on it parent view
Create a View:
This sample, it depends on your view which init method, you have created, used that one.
MyCustomView *customView = MyCustomView(frame: CGRectZero(top: 0, left: 0, width: 200, height: 50)
self.view.addSubview(customView)
If you want to use IBoutlet you need to remove weak from your IBoutlet after you remove weak you remove and add the view again to your view but you need to set the constraints programmatically to view.
Example:
// removed 'weak' reference
#IBOutlet var mButton: UIButton!
... other functionality
// remove from superview
mButton.removeFromSuperview()
// add again to view
view.addSubview(mButton)
// set re-set constraints
NSLayoutConstraint.activate([
mButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
mButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40)
])

Resources