Navigation Bar is Black during Custom Unwind Segue - uinavigationbar

I made a custom UIStoryboardSegue but when it unwinds, it seems to cause my UINavigationBar to go black, then back to it's correct color. See the GIF below.
My custom segue just makes the the new ViewController come down from the top and leave going back up to the top.
Here is the UIStoryboardSegue code:
import UIKit
class SlideDownSegue: UIStoryboardSegue {
var duration: Double = 0.5
override func perform() {
let screenHeight = UIScreen.main.bounds.size.height
let toVC = self.destination
let fromVC = self.source
toVC.view.transform = CGAffineTransform(translationX: 0, y: -screenHeight)
UIApplication.shared.keyWindow?.insertSubview(toVC.view, aboveSubview: fromVC.view)
UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
toVC.view.transform = CGAffineTransform.identity
}, completion: {
success in
fromVC.present(toVC, animated: false, completion: nil)
})
}
}
class UnwindSlideDownSegue: UIStoryboardSegue {
override func perform() {
let screenHeight = UIScreen.main.bounds.size.height
let toVC = self.destination
let fromVC = self.source.parent!
fromVC.view.superview?.insertSubview(toVC.view, at: 0)
UIView.animate(withDuration: 0.5, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
fromVC.view.transform = CGAffineTransform(translationX: 0, y: -screenHeight - 100)
}, completion: {
success in
fromVC.dismiss(animated: false, completion: nil)
})
}
}
If I let the unwind do the default where it just leaves by going to to the bottom of the screen but keep my custom for showing the new View, the UINavigationBar maintains it's correct color, it's only when I used my code provided for unwind that the UINavigationBar goes black during the animation.
Any hints would be much appreciated.
---EDIT---
I played with it a little, if I go into the AppDelegate and change UINavigationBar.appearance().isTranslucent = false to true, I instead get a white background, but then it just appears that the Navigation Bar is suddenly appearing. I'm wondering if it is for some reason being unloaded and then loaded back in once the View Controller is active.
---EDIT 2---
I am sorta able to fix it with a hack. In the AppDelegate inside func application(... didFinishLaunchingWithOptions ...) I added in self.window?.backgroundColor = UIColor.{your color} but all that does is make that black part now appear my color, the Navigation Bar is still disappearing during the segue for some reason.

This is a perfect opportunity to use snapshotView(afterScreenUpdates:). That's how UIKit performs many of their stock transitions (even in UINavigationController) and it's a tremendous way to perform custom transitions, particularly if you want to apply effects. Take a snapshot of the destination (which will have an intact navigation bar), insert it right below the source, perform the animation, dismiss the source so that the destination is ready, and finally remove the snapshot which you won't even notice because it will mirror the destination exactly.

If you want a simple trick ,try to set background color to window in AppDelegate's didFinishLaunchingWithOptions to your navigationbar's color, it works for me :)
like this
window?.backgroundColor = myColor

Related

When a custom segue is executed, the screen freezes and a EXC_BAD_ACCESS message appears

I building my first app in swift and created a custom segue so the slides of my storyboard are presented (sliding) from left to right. The code in my custom segue class, which I got from iOS Segue - Left to Right -, is below:
import UIKit
class SegueFromLeft: UIStoryboardSegue {
override func perform() {
let src = self.source
let dst = self.destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: -src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: .curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
}
)
}
}
I changed some of the segues in my storyboard to this custom segue. The segue connecting my initial view controller to a navigation controller which then connected to another view controller functions correctly. However, when a different segue is executed, the animation occurs, but the screen freezes immediately after.
This snapshot of my storyboard that shows the functioning and dysfunctional segues
After the simulator freezes, an error marks this line in the SegueFromLeft class :
src.present(dst, animated: false, completion: nil)
The error message states "Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee5c39fb8)", which I believe has something to do with memory allocation, but don't know how to fix it or why it is occurring.
All help in alleviating this error is greatly appreciated.
Thanks!

custom segue that moves only part of content xcode

I have seen a lot of tutorial but did not see any interesting me. I want to make custom segue that moves only one view, not the whole page.
Something like this:
Top view is just poping without any animation. But bottom view normally slides on bottom viewcontroler
I have tried:
swift
override func perform() {
scale()
}
func scale(){
let toViewController = self.destination
let fromViewController = self.source
let containerView = fromViewController.view.superview
let originalCenter = fromViewController.view.center
toViewController.view.viewWithTag(1)?.viewWithTag(2)?.transform = CGAffineTransform(translationX:self.source.view.frame.width,y:0)
toViewController.view.center = originalCenter
containerView?.addSubview(toViewController.view)
UIView.animate(withDuration: 0.5, delay:0, options: .showHideTransitionViews ,animations:{
toViewController.view.viewWithTag(1)?.viewWithTag(2)?.transform = CGAffineTransform.identity
},completion:{success in fromViewController.present(toViewController,animated: false,completion: nil)})
}
If you do not understand, ask me and help me please.
I think you need this hierarchy
MainView
-topView
-containerView
-navigation
-firstvc
-secondvc
and set navigationBar to hidden in storyboard

UIViewController In-Call Status Bar Issue

Issue:
Modally presented view controller does not move back up after in-call status bar disappears, leaving 20px empty/transparent space at the top.
Normal : No Issues
In-Call : No Issues
After In-Call Disappears:
Leaves a 20px high empty/transparent space at top revealing orange view below. However the status bar is still present over the transparent area. Navigation Bar also leaves space for status bar, its' just 20px too low in placement.
iOS 10 based
Modally presented view controller
Custom Modal Presentation
Main View Controller behind is orange
Not using Autolayout
When rotated to Landscape, 20px In-Call Bar leaves and still leaves 20px gap.
I opt-out showing status bar in landscape orientations. (ie most stock apps)
I tried listening to App Delegates:
willChangeStatusBarFrame
didChangeStatusBarFrame
Also View Controller Based Notifications:
UIApplicationWillChangeStatusBarFrame
UIApplicationDidChangeStatusBarFrame
When I log the frame of presented view for all four above methods, the frame is always at (y: 0) origin.
Update
View Controller Custom Modal Presentation
let storyboard = UIStoryboard(name: "StoryBoard1", bundle: nil)
self.modalVC = storyboard.instantiateViewController(withIdentifier: "My Modal View Controller") as? MyModalViewController
self.modalVC!.transitioningDelegate = self
self.modalVC.modalPresentationStyle = .custom
self.modalVC.modalPresentationCapturesStatusBarAppearance = true;
self.present(self.modalVC!, animated: true, completion: nil)
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
toViewController!.view.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.0, options: [.curveEaseOut], animations: { () -> Void in
toViewController!.view.transform = CGAffineTransform.identity
}, completion: { (completed) -> Void in
transitionContext.completeTransition(completed)
})
}
I've been looking for a solution for 3 days. I don't like this solution but didn't found better way how to fix it.
I'he got situation when rootViewController view has bigger height for 20 points than window, when I've got notification about status bar height updates I manually setup correct value.
Add method to the AppDelegate.swift
func application(_ application: UIApplication, didChangeStatusBarFrame oldStatusBarFrame: CGRect) {
if let window = application.keyWindow {
window.rootViewController?.view.frame = window.frame
}
}
After that it works as expected (even after orientation changes).
Hope it will help someone, because I spent too much time on this.
P.S. It blinks a little bit, but works.
I faced this problem too but after I put this method, problem is gone.
iOS has its default method willChangeStatusBarFrame for handling status bar. Please put this method and check it .
func application(_ application: UIApplication, willChangeStatusBarFrame newStatusBarFrame: CGRect) {
UIView.animate(withDuration: 0.35, animations: {() -> Void in
let windowFrame: CGRect? = ((window?.rootViewController? as? UITabBarController)?.viewControllers[0] as? UINavigationController)?.view?.frame
if newStatusBarFrame.size.height > 20 {
windowFrame?.origin?.y = newStatusBarFrame.size.height - 20
// old status bar frame is 20
}
else {
windowFrame?.origin?.y = 0.0
}
((window?.rootViewController? as? UITabBarController)?.viewControllers[0] as? UINavigationController)?.view?.frame = windowFrame
})
}
Hope this thing will help you.
Thank you
I had the same issue with the personnal hospot modifying the status bar.
The solution is to register to the system notification for the change of status bar frame, this will allow you to update your layout and should fix any layout issue you might have.
My solution which should work exactly the same for you is this :
In your view controller, in viewWillAppear suscribe to the UIApplicationDidChangeStatusBarFrameNotification
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(myControllerName.handleFrameResize(_:)), name: UIApplicationDidChangeStatusBarFrameNotification, object: nil)
Create your selector method
func handleFrameResize(notification: NSNotification) {
self.view.layoutIfNeeded() }
Remove your controller from notification center in viewWillDisappear
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidChangeStatusBarFrameNotification, object: nil)
You also need your modal to be in charge of the status bar so you should set
destVC.modalPresentationCapturesStatusBarAppearance = true
before presenting the view.
You can either implement this on every controller susceptible to have a change on the status bar, or you could make another class which will do it for every controller, like passing self to a method, keep the reference to change the layout and have a method to remove self. You know, in order to reuse code.
I think this is a bug in UIKit. The containerView that contains a presented controller's view which was presented using a custom transition does not seem to move back completely when the status bar returns to normal size. (You can check the view hierarchy after closing the in call status bar)
To solve it you can provide a custom presentation controller when presenting. And then if you don't need the presenting controller's view to remain in the view hierarchy, you can just return true for shouldRemovePresentersView property of the presentation controller, and that's it.
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PresentationController(presentedViewController: presented, presenting: presenting)
}
class PresentationController: UIPresentationController {
override var shouldRemovePresentersView: Bool {
return true
}
}
or if you need the presenting controller's view to remain, you can observe status bar frame change and manually adjust containerView to be the same size as its superview
class PresentationController: UIPresentationController {
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
NotificationCenter.default.addObserver(self,
selector: #selector(self.onStatusBarChanged),
name: .UIApplicationWillChangeStatusBarFrame,
object: nil)
}
#objc func onStatusBarChanged(note: NSNotification) {
//I can't find a way to ask the system for the values of these constants, maybe you can
if UIApplication.shared.statusBarFrame.height <= 20,
let superView = containerView?.superview {
UIView.animate(withDuration: 0.4, animations: {
self.containerView?.frame = superView.bounds
})
}
}
}
I've been looking for a solution to this problem. In fact, I posted a new question similar to this one. Here: How To Avoid iOS Blue Location NavigationBar Messing Up My StatusBar?
Believe me, I've been solving this for a couple of days now and it's really annoying having your screen messed up because of the iOS's status bar changes by in-call, hotspot, and location.
I've tried implementing Modi's answer, I put that piece of code in my AppDelegate and modified it a bit, but no luck. and I believe iOS is doing that automatically so you do not have to implement that by yourself.
Before I discovered the culprit of the problem, I did try every solution in this particular question. No need to implement AppDelegate's method willChangeStatusBar... or add a notification to observe statusBar changes.
I also did redoing some of the flows of my project, by doing some screens programmatically (I'm using storyboards). And I experimented a bit, then inspected my previous and other current projects why they are doing the adjustment properly :)
Bottom line is: I am presenting my main screen with UITabBarController in such a wrong way.
Please always take note of the modalPresentationStyle. I got the idea to check out my code because of Noah's comment.
Sample:
func presentDashboard() {
if let tabBarController = R.storyboard.root.baseTabBarController() {
tabBarController.selectedIndex = 1
tabBarController.modalPresentationStyle = .fullScreen
tabBarController.modalTransitionStyle = .crossDissolve
self.baseTabBarController = tabBarController
self.navigationController?.present(tabBarController, animated: true, completion: nil)
}
}
I solve this issue by using one line of code
In Objective C
tabBar.autoresizingMask = (UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleTopMargin);
In Swift
self.tabBarController?.tabBar.autoresizingMask =
UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleTopMargin.rawValue)))`
You just need to make autoresizingMask of tabBar flexible from top.
In my case, I'm using custom presentation style for my ViewController.
The problem is that the Y position is not calculated well.
Let's say the original screen height is 736p.
Try printing the view.frame.origin.y and view.frame.height, you'll find that the height is 716p and the y is 20.
But the display height is 736 - 20(in-call status bar extra height) - 20(y position).
That is why our view is cut from the bottom of the ViewController and why there's a 20p margin to the top.
But if you go back to see the navigation controller's frame value.
You'll find that no matter the in-call status bar is showing or not, the y position is always 0.
So, all we have to do is to set the y position to zero.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let f = self.view.frame
if f.origin.y != 0 {
self.view.frame = CGRect(x: f.origin.x, y: 0, width: f.width, height: f.height)
self.view.layoutIfNeeded()
self.view.updateConstraintsIfNeeded()
}
}
Be sure to set the frame of the view controller's view you are presenting to the bounds of the container view, after it has been added to the container view. This solved the issue for me.
containerView.addSubview(toViewController.view)
toViewController.view.frame = containerView.bounds

Custom animation of segue is not played the first time tapping the button

I encountered an unusual behavior on which I am stuck a little, the problem is the following.
I'm using BWWalkthrough library in order to have a 4 slides as launch screen. So in my appdelegate i have the following code which initialize the viewcontrollers:
let storyboard = UIStoryboard(name: "SlidesFlow", bundle: nil)
let walkthrough = storyboard.instantiateViewController(withIdentifier: "SlidesView") as! BWWalkthroughViewController
let page_zero = storyboard.instantiateViewController(withIdentifier: "page_1")
let page_one = storyboard.instantiateViewController(withIdentifier: "page_2")
let page_two = storyboard.instantiateViewController(withIdentifier: "page_3")
let page_three = storyboard.instantiateViewController(withIdentifier: "page_4")
walkthrough.delegate = self
walkthrough.addViewController(page_zero)
walkthrough.addViewController(page_one)
walkthrough.addViewController(page_two)
walkthrough.addViewController(page_three)
Everything works as intended, so no problem here. On the viewController page_three i have a button which redirect me to an other view controller using a custom segue animation
class sentSegueFromRight: UIStoryboardSegue {
override func perform()
{
let src = self.source as UIViewController
let dst = self.destination as UIViewController
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: UIViewAnimationOptions.curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
}
)
}
}
Now the problem is, if i use the same code on a normal viewcontroller the button and the animation work without issues. The problem is when i use the segue defined above from the last slide of my BWWalkthrough. The first time i tapp the button the viewcontroller which should appear does appear but without the corresponding animation. After closing it and taping on the button again the animation is played but an error is returned:
Presenting view controllers on detached view controllers is
discouraged
If i use the button with the standard animation ( without using my custom animation code ) i get no error and the default animation is played.
I can't seem to find a solution to this problem. Does anybody stumbled upon something like this?
The problem here lies in the BWWalkthrough library which is using a scrollview to present all the views of the various ViewControllers that you add.
As such, you add the dst.view at the beginning of the scrollview (at offset screenwidth,0) which you then transform to offset (0,0).
All of this is offscreen as you are currently in the third screen of the walkthrough (at offset (screenwidth*3,0)). As such you don't get to see the animation and directly see the presented view controller when the segue ends.
To remedy this, add your dst.view in the segue to the superview of the scrollview. i.e. instead of src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
write src.view.superview?.superview?.insertSubview(dst.view, aboveSubview: src.view) in the segue. (Assuming that you are using the segue only from the walkthrough)
If you intend to use the segue in other places too, then you can maybe add a type check in the segue to check if the superview of src.view is a scrollview, if yes, add dst.view to the superview of the scrollview.

Why call presentViewController in the end of custom segue, it cause viewDidAppear to be called twice?

In my UIStoryboardSegue subclass, I use presentViewController at the end of the perform method, this cause viewDidAppear/viewWillAppear to be called twice?
How can I prevent this?
Thanks
Current code:
override func perform() {
// Assign the source and destination views to local variables
let sourceView = sourceViewController.view as UIView!
let destView = destinationViewController.view as UIView!
// Get the screen width and height
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view
destView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
// Add the destination view to the window
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(destView, aboveSubview: sourceView)
// Animate the transition
UIView.animateWithDuration(0.7, animations: { () -> Void in
// Scale down the source view
sourceView.transform = CGAffineTransformScale(sourceView.transform, 0.90, 0.90)
}) { (Finished) -> Void in
}
UIView.animateWithDuration(1.0, delay: 0.2, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.7, options: .CurveEaseOut, animations: { () -> Void in
destView.frame = CGRectOffset(destView.frame, 0.0, -screenHeight)
}) { (finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
}
}
I see now that without presentViewController the new controller is destroyed soon after the animation custom segue. I see why we need it.
The reason why viewDidAppear gets called twice is that you first insert the viewcontroller as a subview into the keyWindow. (which triggers the viewDidAppear)
and then present the viewcontroller. (which triggers the viewDidAppear also)
This brings also some side effects to your app because you will end up with a viewHierachy like:
-- KeyWindow
---- FirstVC View
---- SecondVC View
---- UITransitionView (because of the presentVC)
------- SecondVC View
If you want to achieve this swipe/scroll transition why not just use a scrollView and have childviewcontrollers in it?
The other solution for this would be to use a custom transition and simply call presentViewController with a custom transition as described here: http://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions

Resources