I have a modal view controller A presented at the top of the view controllers hierarchy.
Is it possible to make it fullscreen temporarily when a user presses a button? And then go back to standard modal look when used presses another button?
To clarify, I'm not talking about modalPresentationStyle = .fullScreen BEFORE A is presented, I need to present A in a standard way and then stretch it to be fullscreen when needed.
Although if I write pseudo code it would be something like this:
class A: UIViewController {
#objc func goFullScreen(sender: UIButton) {
// pseudo code:
// modalPresentationStyle = .fullScreen
// go fullscreen, block `drag-to-dismiss` gesture, remove `cornerRadius`, set `self.view.frame = UIScreen.main.bounds`
}
#objc func cancelFullScreen(sender: UIButton) {
// pseudo code:
// modalPresentationStyle = .pageSheet
// return to the standard look of a modal view controller
}
}
I found UIAdaptivePresentationControllerDelegate.presentationControllerShouldDismiss method that intercepts drag to close gesture, but I don't see any way to go fullscreen.
Picture of what I am talking about:
ps. I know I can make a custom container view controller with a custom transition delegate but I'd like to know if it's possible to implement such behaviour with system classes because I basically need just to flip modalPresentationStyle property with some interpolation animation...
Also on the right I hide/show some UI elements, but still it's the same view controller
Related
In iOS 10, I could determine the list of navigation bar buttons I want to appear in viewDidLoad, and they would appear in the navigation bar as the view controller transitioned in.
In iOS 11, I can no longer do this. Whatever navigation bar buttons were set in interface builder are displayed as the view controller transitions in, and my custom list of buttons are not displayed until after the view finishes sliding in. Updating the buttons in viewWillAppear does not help.
Is this simply a bug in iOS 11, or is there something else I should be doing? Is there a workaround so I can continue to display buttons while the screen loads?
In the following example, I have set a button "Default Button" in the storyboard, and override it with an "Edit" button in viewDidLoad. The project is available on Github.
iOS 10
iOS 11
It looks like the issue is that navigation bar icons displayed during the transition appear to be fixed when the view controller is passed off to the navigation controller. By the time that viewDidLoad is called, the icons have already been fixed.
In order to fix this, we need to update the navigation bar icons on the view controller's navigationItem before the view controller is pushed onto the navigation controller stack.
One way to do this would be to setup the navigation bar icons in awakeFromNib. This is what #Joe's answer was effectively doing, because apparently viewDidLoad is called from awakeFromNib when isViewLoaded is true during awakeFromNib.
However, doing this in awakeFromNib prevents you from taking into account any properties set on the view controller in prepareForSegue. So another option (and the one that I am using) is to force the entire view to load in prepareForSegue by adding the line _ = controller.view after setting any desired properties.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
...
_ = controller.view
}
}
}
Move your Edit barButton code from viewDidLoad to isViewLoaded method as below.
override var isViewLoaded: Bool {
self.navigationItem.rightBarButtonItem = self.editButtonItem
return true
}
Output:
Note: Above code will fix the transition delay issue. Really, don't have much to explain why this happening. I experienced similar issue in iOS 10 as well NavigationBar delay updating barTintColor iOS10. It could be another bug in iOS11.
PS:
After reading Apple Doc about isViewLoaded and comparing other view loading methods. Its all about loading memory upon view loads.
You don't really need to move you barButton code at all.Just implement the isViewLoaded method and return to true as below:
override var isViewLoaded: Bool { return true}
I have a table view controller and another view controller. Now my requirement is that i need to swipe the table view controller half over the another view controller when i swipe the view controller. The image i can show is like this:
Is it possible to achieve this by using the Swipegesture . If possible how can i do this in Swift3?
While there are libraries out there to do this for you, if you are going to do this yourself, the basic idea is that you can create a "swipe from edge" gesture recognizer to initiate a custom presentation of a view controller (that has your menu on it). It consists of:
a custom transitioning delegate (conforming to UIViewControllerTransitioningDelegate protocol) that specifies the animation controller and interaction controller (both described below) to be used during the presentation of the next scene;
an animation controller (conforming to UIViewControllerAnimatedTransitioning) that dictates the animation of the scene from the right edge;
an interaction controller (a UIPercentDrivenInteractiveTransition) that you can use to optionally drive the transition via a gesture;
a presentation controller (a UIPresentationController subclass) that dictates whether the presented view will be removed or not (in this case you do not want it removed), as well as any other chrome to be animated alongside the animated presentation of the new scene (e.g. you often dim or blur the presenting view); and
gesture recognizers to drive the interaction controller.
For more information, see WWDC videos Custom Transitions Using View Controllers and A Look Inside Presentation Controllers.
See https://github.com/robertmryan/SwiftCustomTransitions/tree/rightside for a Swift 3 example that renders the following UX:
This is all admittedly complicated enough that you may well want to consider third party libraries for presenting slide-in menus. But if you wanted to "roll your own", these are the basic pieces involved.
What I would do is first create and hide a UIView that lets you select those UIViewController on the right side of the screen and animate it to show when the user swipes.
Then you implement this method that returns UIViewControllerAnimatedTransitioning, a class that you want to implement for custom transitioning.
- (id<UIViewControllerAnimatedTransitioning>)
navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
This could be a good tutorial for custom transitioning.
https://www.raywenderlich.com/110536/custom-uiviewcontroller-transitions
I would look into SWRevealViewController. It gives you the behavior you are looking for in just a few lines of code. You can present an SWRevealViewController that will hold your top and bottom UIViewController and present that as your scene.
// Your Front View Controller
let frontStoryboard = UIStoryboard(name: "FrontStoryboard", bundle: .main)
let frontVC = frontStoryboard.instantiateInitialViewController()
// Your Rear View Controller
let rearStoryboard = UIStoryboard(name: "RearStoryboard", bundle: .main)
let rearVC = rearStoryboard.instantiateInitialViewController()
// Create Reveal View Controller From Both
if let revealVC = SWRevealViewController(rearViewController: rearVC, frontViewController: frontVC) {
present(revealVC, animated: true) {
}
}
Using SWRevealViewController you can set the UIViewController that you are trying to do the sliding in as the frontViewController and you can present the rearViewController simply using a single line of code.
// When you import SWRevealViewController every UIViewController
// has a method revealViewController() that returns the revealViewController
// that you can tell to toggle it's reveal state using the below
self.revealViewController().revealToggle(animated: true)
EDIT
I believe you are trying to build a sliding navigation menu. Here is a link to a Ray Wenderlich tutorial where he is creating exactly the navigation you are looking for.
I am finishing my career project and I want to make some UI improvements on the application I developed in Swift for iOS.
The application does not have any explicit explanation of how it works and I'd like to detail it in a pop-up View called from an information button in the Navigation Bar. I'd like some kind of View similar to the Notification Alerts, which pop-up in front of the current view and they disappear by pressing another button. I give you an example image:
I want this button to be available from any Tab View (I've got three views). Any idea of how to manage that?
Thank you very much!
First, go to IB and create a new View Controller. In the Attributes Inspector, click on the dropdown next to Size and click Freeform. Set the size to what you want. Then click Use Preferred Explicit size.
Control-drag from your info button to the new VC you created. Click on the segue, then go to the attributes inspector and change the segue type to 'Present As Popover.' Change the segue identifier to whatever you want. I named it popover.
Create a new class for your popover view and assign it to the VC you created. It doesn't need any code at the moment.
In your presenting VC (Tab Bar), add UIPopoverPresentationControllerDelegate to your class definition. Then add this function:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
Next, in your Tab Bar VC, add this code to your prepareForSegue method:
if segue.identifier == "popover" {
let controller = segue.destinationViewController as! PopoverVC // whatever you named the class
controller.modalPresentationStyle = UIModalPresentationStyle.Popover
controller.popoverPresentationController!.delegate = self
}
That should do it.
I want to show one view when the segmented control highlights the first option. When the user highlights the other option, I want the first view to disappear (or hide) and the other view to become visible. Then, if the user presses the first option again, the second view hides and the first becomes visible.
What is the best way to do this?
I do not want to switch ViewControllers, but simply Views who are both using the same ViewController.
A way to do that would be to give a tag to each view (as in your segment's indices) and call a method when the its value is changed that would hide all view's accept the one with the right tag number.
You could hook the UISegmentedControl from the view controller to the code using an #IBAction and use the following method:
#IBAction func switchView(sender: UISegmentedControl) {
// Change your view controller's view property
// to reference whatever custom views you have.
if(sender.selectedSegmentIndex == 0) {
self.view = viewOne
} else {
self.view = viewTwo
}
}
viewOne and viewTwo being UIViews
The main navigation for my iOS 8 app is a tab bar with 3 tabs. I also have a login screen that is a UIViewController that should not have tabs at the bottom. I only ever access the login screen programmatically from the normal flow if I detect that a user is either not logged in, or has chosen to log out. I'm not sure how to remove the tabs from the screen when I show the login screen with the following code:
let loginView = self.storyboard?.instantiateViewControllerWithIdentifer("Login") as UIViewController
self.presentViewController(loginView, animate: true, completion: nil)
I have tried setting the tabBar.hidden property to true with no result. I have also tried modifying the storyboard to set "Bottom Bar" to "None" and have checked "Hide Bottom Bar on Push".
Maybe the login screen's behavior is different from the main screen. So you might want to create another ViewController for the Login screen.
I have tried setting the tabBar.hidden property to true with no result.
Does it finish to load the tab bar? Call hidden=YES after viewDidLoad of your loginView.
EDIT
After you call self.presentViewController(loginView, ...), the UITabBarController's view will be removed from UIWindow, and the loginView's view will be inserted to UIWindow's subview. So the tab bar is invisible now.
You can use View Debugging feature of XCode6 to understand the view hierarchy. You can use UIViewController's recursiveDescription() method too.
/* UIViewExtension.h */
#interface UIView(MyExtension)
- (NSString*)recursiveDescription; //This is hidden API so need the declaration.
#end
/* YourViewController.swift */
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println(self.tabBarController?.view.superview?.recursiveDescription())
// Here, the view of tabBarController is subview of UIWindow.
}
#IBAction func testButtonPressed(sender: UIButton) {
self.presentViewController(viewController, animated: true) { () -> Void in
println(self.tabBarController?.view.superview?.recursiveDescription())
println(loginView.view.superview?.superview?.recursiveDescription())
//Now, TabBarController's view is not subView of UIWindow.
}
}