In implementing the following cross-dissolve custom segue on a button press (learned at https://www.youtube.com/watch?v=xq9ZVsLNcWw):
override func perform() {
var src:UIViewController = self.sourceViewController as! UIViewController
var dstn:UIViewController = self.destinationViewController as! UIViewController
src.view.addSubview(dstn.view)
dstn.view.alpha = 0
UIView.animateWithDuration(0.75 , delay: 0.1, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: { () -> Void in
dstn.view.alpha = 1
}) { (finished) -> Void in
dstn.view.removeFromSuperview()
src.presentViewController(dstn, animated: false, completion: nil)
}
}
I am getting the "Unbalanced calls to begin/end appearance transitions" warning/error.
I have thoroughly searched many stackoverflow questions:
Unbalanced calls to begin/end appearance transitions for <UITabBarController: 0x197870>
my response: I am using the segue as a button press not within a TabBar Controller so this answer does not apply
Keep getting "Unbalanced calls to begin/end appearance transitions for <ViewController>" error
my response: I tried two methods: 1) All segues done programatically with self.performseguewithidentifier 2) All segues done via interface builder, by click dragging the buttons and selecting my custom transition from the attributes pane of the selected segue. Both still yielded the aforementioned error.
"Unbalanced calls to begin/end appearance transitions" warning when push a view in a modal way in XCode 4 with Storyboard
my response: Same as above, double checked all segues were only performed once, either programmatically or via interface builder, not both.
"Unbalanced calls to begin/end appearance transitions for DetailViewController" when pushing more than one detail view controller
my response: I am not using a table view controller nor a navigation controller in my segues, so this answer does not apply.
Unbalanced calls to begin/end appearance transitions for UITabBarController
my response: I tried making all segues perform within a dispatch as shown below
let delay = 0.01 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
self.performSegueWithIdentifier("toNonFBLogin", sender: nil)
}
however still to no avail, the warning/error still pops up.
I'm aware what the error means, that it's attempting to present a new viewcontroller before the previous one loads, but not sure how to tackle it.
The only lead I have is that the previous (source) viewcontroller pops up real quickly before the final viewcontroller loads, as seen at 0:04 at
https://youtu.be/i1D5fbcjNjY
The glitch only actually appears I would guess around 5% of the time, however.
Any ideas?
Looks like this behaviour is the result of some - as of yet - undocumented change in recent iOS releases.
Getting frustrated with your exact issue and the lack of satisfying answers I've come up with this:
// create a UIImageView containing a UIImage of `view`'s contents
func createMockView(view: UIView) -> UIImageView {
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, UIScreen.mainScreen().scale)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImageView(image: image)
}
override func perform() {
let src:UIViewController = self.sourceViewController as! UIViewController
let dstn:UIViewController = self.destinationViewController as! UIViewController
let mock = createMockView(dstn.view)
src.view.addSubview(mock)
mock.alpha = 0
UIView.animateWithDuration(0.75, delay: 0.1, options: UIViewAnimationOptions.TransitionCrossDissolve,
animations: { () -> Void in
mock.alpha = 1
},
completion: { (finished) -> Void in
src.presentViewController(dstn, animated: false, completion: { mock.removeFromSuperView()})
})
}
createMockView() will create a UIImageView containing a snapshot of the destination VCs contents and use that to do the transition animation.
Once the transition is finished the real destination VC is presented without animation causing a seamless transition between both. Finally once the presentation is done the mock view is removed.
Related
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
I have looked at several examples and it looks like this used to be the default effect for a Push segue which is now deprecated. The new default animation for a Show or Present Modally segue is to have the new view slide in from the bottom to the top.
I am using the code below after looking through several examples of how to create your own custom segue. I am getting the effect that I want which is to have the new view slide in from the right to the left right until the end of the animation. Right at the end, when the new view is fully displayed on the screen, there is a fort of blink, where for a few seconds I see the old view and then the new view is presented again.
Please help! Thanks.
class CustomStoryboardSegue: UIStoryboardSegue {
override func perform() {
let sourceVC = self.sourceViewController
let destinationVC = self.destinationViewController
sourceVC.view.addSubview(destinationVC.view)
destinationVC.view.transform = CGAffineTransformMakeTranslation( sourceVC.view.frame.size.width, 0)
UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
destinationVC.view.transform = CGAffineTransformMakeTranslation(1.0, 1.0)
}) { (finished) -> Void in
destinationVC.view.removeFromSuperview()
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
sourceVC.presentViewController(destinationVC, animated: false, completion: nil)
}
}
}
}
UPDATE
To clarify the finished block in my code above, I delay the presentation of the view controller by a second to avoid getting the
error: Unbalanced calls to begin/end appearance transitions for TableViewController
because I cannot remove the source view and present the destination view at the exact same time.
To test this I removed the delay and updated my finished block as follows and now I am getting the 'unbalanced call error' as expected but, interestingly enough, I am also seeing the blink at the end of the transition.
}) { (finished) -> Void in
destinationVC.view.removeFromSuperview()
sourceVC.presentViewController(destinationVC, animated: false, completion: nil)
}
My navigation controller intermittently will freeze on push. It seems to add the new view controller onto the stack, but the animation never takes place. I also have two other containers that hold view controllers on the screen, and I can interact with both of them just fine after the navigation controller freezes. The really interesting thing is if I try to push another view controller onto the navigation controller's stack, I noticed that there is an extra view controller on top of the stack (the view controller that I pushed initially that froze the navigation controller). So if I'm on the home screen (we'll call it VC-Home) and I try to push a new view (VC-1) and it freezes, then I try to push a new view (VC-2), this is what I see in the current stack before the push:
{ [VC-Home, VC-1] }
and after pushViewController is called, it remains the same; VC-2 is not added to the stack.
From what I can tell, the navigation controller starts the animation by making the previous view controller inactive before the animation begins, but then the animation never takes place, leaving the navigation controller in a frozen state.
I'm creating the new view controller from a storyboard by calling
UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController") so I don't think there's any issues there. I'm also not overriding pushViewController on the navigation bar. Some unique things about my app is that it is very high-res image heavy (using SDWebImage to manage that) and I always have three containers on the screen at once (one navigation controller, one view controller for search, and one interactive gutter/side/slideout menu).
CPU usage is low and memory usage is normal (steadily around 60-70MB on device when freezes occur).
Are there any ideas with what might be causing this or any debugging tips that could help me discover the real problem?
Update
There's no unique code for the UINavigationController since I'm just pushing using pushViewController(). Here's the code that calls it:
func didSelectItem(profile: SimpleProfile) {
let vc = UIStoryboard.profileViewController()
vc.profile = profile
navigationController?.pushViewController(vc, animated: true)
}
The ViewController that I pushed has the following code in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
button.roundView()
if let type = profile?.profileType {
//load multiple view controllers into a view pager based on type
let viewControllers = ProfileTypeTabAdapter.produceViewControllersBasedOnType(type)
loadViewPagerViews(viewControllers)
let topInset = headerView.bounds.height + tabScrollView.contentSize.height
if let viewPager = viewPager {
for view in viewPager.views {
if let tempView = view as? PagingChildViewController {
tempView.profile = fullProfile
tempView.parentVCDelegate = self
tempView.topInset = topInset
}
}
}
}
}
func loadViewPagerViews(viewControllers: [UIViewController]) {
viewPager?.views = viewControllers
viewPager?.delegate = self
//loading views into paging scroll view (using PureLayout to create constraints)
let _ = subviews.map { $0.removeFromSuperview() }
var i = 0
for item in views {
addSubview(item.view)
item.view.autoSetDimensionsToSize(CGSize(width: tabWidth, height: tabHeight))
if i == 0 {
item.view.autoPinEdgeToSuperviewEdge(.Leading)
} else if let previousView = views[i-1].view {
item.view.autoPinEdge(.Leading, toEdge: .Trailing, ofView: previousView)
}
if i == views.count {
item.view.autoPinEdgeToSuperviewEdge(.Trailing)
}
i += 1
}
contentSize = CGSize(width: Double(i)*Double(tabWidth), height: Double(tabHeight))
}
Update 2
I finally got it to freeze again. The app was in the background and I brought it back and tried pushing a view controller on the stack when it froze. I noticed an animation was taking place. I have a scrollview at the top of the page that pages through its content every 10 seconds (think of the app stores top banner). On this freeze, I noticed that the banner was mid-animation.
Here's the scrolling function from the my UIScrollView that gets called every 10 seconds:
func moveToNextItem() {
let pageWidth: CGFloat = CGRectGetWidth(frame)
let maxWidth: CGFloat = pageWidth * CGFloat(max(images.count, profileImages.count))
let contentOffset: CGFloat = self.contentOffset.x
let slideToX = contentOffset + pageWidth
//if this is the end of the line, stop the timer
if contentOffset + pageWidth == maxWidth {
timer?.invalidate()
timer = nil
return
}
scrollRectToVisible(CGRectMake(slideToX, 0, pageWidth, CGRectGetHeight(frame)), animated: true)
}
I don't recall ever having a push stop because of an animation/scroll taking place, but I could be wrong.
I've also rechecked the stack and the same situation as described above is still the case where [VC-Home, VC-1] is the stack and VC-2 is not pushed on. I've also gone through VC-1's variables and everything has loaded (data calls and image loads).
Update 3
This is getting stranger by the second. I've overriden pushViewController so I can put a breakpoint in there and do some debugging based on Alessandro Ornano's response. If I push a view controller unsuccessfully, then send my app to the background, put a breakpoint into the pushViewController call, and bring the app back, the breakpoint is immediately hit a number of times. If I then continue past all the hits, the next view controller suddenly becomes visible and the last view controller I tried to push is now on the stack as the last view controller. This means that the one that I see is still disabled, which essentially puts me in the same position as before.
We have faced the same problem couple of weeks back. And for our problem we narrowed it down to left-edge pop gesture recogniser. You can try and check if you can reproduce this problem using below steps
Try using the left edge pop gesture when there are no view controllers below it (i.e on root view controllers, your VC-Home controller)
Try clicking on any UI elements after this.
If you are able to reproduce the freeze, try disabling the interactivePopGestureRecognizer when the view controller stack have only one view controller.
Refer to this question for more details. Below is the code from the link for ease of reference.
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
if (self.viewControllers.count > 1)
{
self.interactivePopGestureRecognizer.enabled = YES;
}
else
{
self.interactivePopGestureRecognizer.enabled = NO;
}
}
}
Great answer by #Penkey Suresh! Saved my day! Here's a SWIFT 3 version with a small addition that made the difference for me:
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if (navigationController.viewControllers.count > 1)
{
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
navigationController.interactivePopGestureRecognizer?.isEnabled = true;
}
else
{
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
navigationController.interactivePopGestureRecognizer?.isEnabled = false;
}
}
Just don't forget to add UINavigationControllerDelegate and set the navigationController?.delegate = self
Another important part is to assign the interactivePopGestureRecognizer to self or to nil accordingly.
I was thinking about intermittently freezing, main thread and SDWebImage.
Assuming that you using the image you downloaded from downloadImageWithURL:options:progress:completed:'s completed block .. if so, make sure you dispatch to the main queue before using using the image.
If you use the SDWebImageDownloader directly, the completion block (as you noted) will be invoked on a background queue, you can fix it using dispatch_async on the main queue from the completion.
Otherwise you can use:
SDWebImageManager downloadImageWithURL:options:progress:completed: (method that invokes the completion blocks on the main queue).
if the problem persist (just because you speaking about "..some unique things about my app is that it is very image heavy..") look also Common problems expecially the Handle image refresh know problems.
Add to your check also this nice snippet code:
import UIKit.UINavigationController
public typealias VoidBlock = (Void -> Void)
public extension UINavigationController
{
public func pushViewController(viewController: UIViewController, animated: Bool, completion: VoidBlock) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.pushViewController(viewController, animated: animated)
CATransaction.commit()
}
}
maybe can help to understand if pushViewController finish, if finish with all viewControllers expected ..
Another test I try to make is to launch the app with iOS 8.x and iPhone 6+, because there are some issues in the pureLayout project around iOS 9. Can you send feedbacks around this test?
I've some suspicious also on the real scrollview dimension before the pushview action, can you analyze the current view by examing the view hierarchy?
Please check if you have any unnecessary codes like below in your BaseNavigationViewController :
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Here I have a sample code which reproduces this issue (freezing app in pushing VC) that you have to reproduce it by these steps:
Try using the (left | right) edge pop gesture when there are no view controllers below it (i.e on root view controllers, your VC-Home controller)
Try clicking on any UI elements after this(which pushes you to next ViewController).
Sample Code: https://github.com/aliuncoBamilo/TestNavigationPushBug
My case solved by restarting Xcode and simulator, then choosing a different device from simulators list.
Answer by #Penkey Suresh! and #Tim Friedland helped me alot, one thing that i had to do extra for my usecase, might help someone else too.
USE CASE:
I had tab bar controller and i wanted swipe-back-gesture on screens let's say i'm on tabA and opened VC1, VC2 from there. My swipe gesture was working correctly on VC1 and VC2 but for some reason it was not disabling when coming back to tabA which is why it was freezing when trying to swipe left from there and trying to click somewhere (as mentioned by #Pankey Suresh)
SOLUTION IN SWIFT 5
I have this custom class implemented:
class BaseSwipeBack: UIViewController, UIGestureRecognizerDelegate {
func swipeToPop(enable: Bool) {
if enable && (navigationController?.viewControllers.count ?? 0 > 1){
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
navigationController?.interactivePopGestureRecognizer?.isEnabled = true;
}
else {
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
navigationController?.interactivePopGestureRecognizer?.isEnabled = false;
}
}
}
USAGE
VC1 class - make it sub class of BaseSwipeBack that we just created so that you can access the functions
class VC1: BaseSwipeBack {
override func viewDidLoad() {
}
override func viewDidAppear(_ animated: Bool) {
self.swipeToPop(enable: true)
}
deinit {
self.swipeToPop(enable: false)
}
}
tabAController class - although i have disabled gesture in deiniting VC1 but it was not working fine so i had to disable it again tabAController
class tabAController: BaseSwipeBack {
override func viewDidLoad() {
}
override func viewDidAppear(_ animated: Bool) {
self.swipeToPop(enable: false)
}
}
I'm using Swift 2 and xcode 7.1.1.
My app is a tabbar + navigationbar based application.
What I'd like to do:
From my first viewcontroller (CollectionVC) I have a collectionview of images(tabbar at bottom and navbar at top). When you select an image, I transition programmatically to a modalviewcontroller (ImageZoomVC) that allows them to zoom on the image (no tabbar or navbar). From the ImageZoomVC I want to transition programmatically to the UserProfileVC. The UserProfileVC should have the tabbar and navbar like the first view. The navbars back button should also take us back to the ImageZoomVC.
This workflow is similar to facebook. When you select an image, you go to a black screen with just that image and some more info. You can click on anyone tagged in the image and it will push their profile onscreen with the tabbar and navbar back in place.
The Problem:
Once in the ImageZoomVC, I can't transition to the UserProfileVC without losing the tabbar and navbar. I've been able to add a navbar programmatically but there is no back button, and setting a back button hasn't worked. Also, there is no tabbar.
Code:
From my CollectionVC, I transition to the ImageZoomVC from my collectioncell like this:
#IBAction func PhotoButtonPressed(sender: UIButton) {
let imageZoom = ImageZoomVC()
imageZoom.showFrom(self.parentView!)
}
Here is the showFrom function that presents the ImageZoomVC modally. This code is inside the ImageZoomVC:
public func showFrom(viewController: UIViewController) {
//Some stuff here, edited for length
viewController.presentViewController(self, animated: false) {
UIView.animateWithDuration(ImageZoomVC.TransitionAnimationDuration,
delay: 0,
options: [.BeginFromCurrentState, .CurveEaseInOut],
animations: {
[weak self] in
self?.collectionView.alpha = 1
self?.collectionView.transform = CGAffineTransformIdentity
},
completion: {
[weak self] finished in
self?.view.userInteractionEnabled = finished
}
)
}
}
This is the code I'm attempting to use to transition to the UserProfileVC with navbar and tabbar. This doesn't work. It will present the VC, but there is no tabbar or back button.
func userSelected (sender: UIButton) {
let vc = self.presentingViewController?.storyboard?.instantiateViewControllerWithIdentifier("User Profile") as! UserProfileVC
let newNav = UINavigationController(rootViewController: vc)
newNav.navigationBar.barTintColor = UIColor().specialPurple()
newNav.navigationBar.translucent = false
self.presentViewController(newNav, animated: true, completion: nil)
}
A couple notes: The ImageZoomVC doesn't exist in storyboard. It is called and instantiated programmatically.
The ImageZoomVC is based off Agrume from Github found here:
https://github.com/JanGorman/Agrume
I'd like to push the UserProfileVC like facebook does. This current code presents it from the bottom. The code pushViewController is not recognized here in xCode.
Let me know what you think. All thoughts are welcome. Thanks.
Attempting to write a custom segue where the source view is scaled out, while changing the alpha of the destination to fade the destination in. The destination is a MKMapView, so I want it updating as the fade occurs.
With what I've tried I end up with the source and designation scaling out simultaneously, and I can't get just the source view to scale out.
class Map_Segue: UIStoryboardSegue {
override func perform()
{
var sourceViewController : UIViewController = self.sourceViewController as UIViewController
var destinationViewController : UIViewController = self.destinationViewController as UIViewController
destinationViewController.view.alpha = 0 sourceViewController.addChildViewController(destinationViewController) sourceViewController.view.addSubview(destinationViewController.view)
UIView.animateWithDuration(2.0,delay:1.0,options: UIViewAnimationOptions.CurveEaseInOut, // delay of 1 second for aesthetics
animations:
{
sourceViewController.view.transform = CGAffineTransformScale(sourceViewController.view.transform, 100.0, 100.0);
destinationViewController.view.alpha = 1;
},
completion:
{ (finished:Bool)->Void in
destinationViewController.didMoveToParentViewController(sourceViewController);
}
)
}
}
I've tried autoresizesSubviews=false, but that doesn't seem to do anything.
I've tried setting the destination transform in the animation to be 1/100 (and set the options to UIViewAnimationOptions.CurveLinear which has the final result correct, but the transition effect is wrong (map in the background scaled up then down again)
I'm sure this should be easy, and I'm missing a trick as I'm new to this.
Anyone got any ideas?
Update:
I've found that (somewhat obviously) I should use sourceViewController.view.superview?.insertSubview( destinationViewController.view, atIndex:0) to insert it alongside the original source view, rather than as a child of it, that way, obviously, the transform is independent, not with respect to the views parent (which it will be as a subview). The problem then is swapping to the new view. Using the method I had, viewWillAppear and similar are not called, and the changing over the views does not work. If I call presentViewController, then we get a glitch when viewWillAppear is called.
Solution so far
Forget using custom segues. Followed the suggestion and placed a UIImageView on top of the map view, and had a beautiful animating fade in about 5 minutes of coding.
I think you are a bit confused with parent and child view controllers etc. It is sufficient to temporarily add the second view controller's view to the first one, perform the transitions and then clean up.
I tested the following in a simple sample project. Note that I am adding the second view controller's view to the window rather than the first view controller's view because otherwise it would also get scaled up.
override func perform() {
let first = self.sourceViewController as ViewController
let second = self.destinationViewController as ViewController
second.view.alpha = 0
first.view.window!.addSubview(second.view)
UIView.animateWithDuration(2.0, delay: 0.0,
options: .CurveEaseInOut, animations: { () -> Void in
first.view.transform = CGAffineTransformMakeScale(100.0, 100.0)
second.view.alpha = 1.0
})
{ (finished: Bool) -> Void in
second.view.removeFromSuperview()
first.presentViewController(second, animated: false, completion: nil)
}
}