I'm working on my first iOS/swift app. It's a modified diary entry app, but I currently have a feature I'm trying to implement where a passcode view controller is presented if the app goes to the background (and then foreground) or is made inactive. The problem i've had is with implementation.
My question: Is there a simple way of presenting the passcodeVC on top of the current vc when it entersforeground and then dismissing it, leaving the current VC.
I understand that other view controllers can determine if the app enters foreground, but i'd imagine it's better to do it with app delegate since it seems to be the universal listener for app states. Provided i have the view controller and passcode features working, how do i get the passcode view to present itself and then dismiss if the code is correct; revealing the screen that was below it (some code would be helpful as I've spent a couple of days and it's a little over my head).
Here was an extension i tried to build:
public extension UIViewController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
func hide() {
dismiss(animated: true, completion: nil)
}
}
Thanks.
Related
as I wrote in the title, I'm not sure why ViewWillAppear is not called when another VC is dismissed. I think my project is a little bit tricky, so I'm gonna explain what is going on in my project.
Mostly, I configure UIs in code. I have two VCs, ListVC and CameraVC, and I have a tab bar and a navigation var in ListVC, which I configured all in code.
So in the SceneDelegate
I wrote something like
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: scene.coordinateSpace.bounds)
window?.windowScene = scene
window?.rootViewController = createTabBar()
window?.makeKeyAndVisible()
}
func createTabBar() -> CustomTabBarController {
let tabbar = CustomTabBarController()
tabbar.viewControllers = tabbar.setUpTabbarItems()
return tabbar
}
and in the CustomTabBarController class, since I just wanted to present the event list, I added
func createListNC() -> UINavigationController {
let ListVC = ListViewController()
ListVC.tabBarItem = UITabBarItem(title: "", image: UIImage(named: "add-icon"), tag: 0)
return UINavigationController(rootViewController: ListVC)
}
func setUpTabbarItems() -> [UIViewController]{
return [createListNC()]
}
So now I can display List VC on the home of the app. However, I made the CameraVC all in the storyboard and I added the following code when a user taps one of the Event cells in List VC and it presents CamearaVC.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Camera", bundle: nil)
let CameraVC = storyboard.instantiateViewController(identifier: "Camera")
self.present(CameraVC, animated: true, completion: nil)
}
So I can also present the CameraVC from ListVC.
But the problem happens when I want to back from CameraVC to ListVC.
I added IBAction to the button saying "< back" in CameraVC, and I can dismiss the CameraVC, however, since the cameraVC's screen is horizontal, the ListVC also stack with horizontal, which I want to make the ListVC vertical.
#IBAction func unwindToLiveList (_ segue: UIStoryboardSegue) {
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
So, I was planning to write a code for fixing the orientation of the app when a user back from CameraVC to ListVC.
I just wanted to know where I should put that fixing orientation code, so I added viewwillappear and some print statements to ListVC, but none of them are called when back from CameraVC to ListVC. I also wrote the same code in CustomTabBarController class, but it is never called...
So, I was wondering where and which file I can write the code for fixing the orientation to trigger when the user came back from CameraVC to ListVC.
Also, if anyone can explain why this is happening, please let me know.
The problem seems to be the way View controllers are presented now. Since the bottom view controller never actually leaves view will appear isn't called when dismissing anymore. I'm pretty sure it did in the past, but now that controllers present as overlays I guess it doesn't anymore.
However, you are in a navigation stack so if you just use that you'll get a free back button and viewWillAppear will get called.
let cameraVC = storyboard.instantiateViewController(identifier: "Camera")
self.navigationController?.pushViewController(cameraVC, animated: true)
As I said you'll get a free back button on cameraVC and that will pop cameraVC off the stack for you.
If you want to keep from using the navigationController for some reason take a look at
https://developer.apple.com/documentation/uikit/uicontentcontainer/1621466-viewwilltransition
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print(size)
}
The reason is about the change they had made in iOS13 (or 14). Before the default modal presentation mode (it seems that you are presenting modally) was full screen, now they are presented over the current context without making the presenting view controller to disappear, that is why willAppear is not called, it' already there.
If you want to change that behavior you can setup a different presentation style as .fullScreen.
This question already has answers here:
How to push and present to UIViewController programmatically without segue in iOS Swift 3
(3 answers)
Closed 3 years ago.
My problem is that I can't find the correct code to transition between two scenes in IOS programatically. I have tried a few example codes, but none of them work as expected.
let vc storyboard!.instantiateViewController(withIdentifier: "savedMode") as! SavedModeViewController
let navigationController = UINavigationController(rootViewController: vc)
self.present(navigationController, animated: true, completion: nil)
This code goes to the new scene, but the back button doesn't appear in the left side of the navigation bar, and transition between scenes is wrong; instead of left to right animation, there is bottom to top animation.
let controller = storyboard?.instantiateViewController(withIdentifier: "savedMode") as! SavedModeViewController
present(controller, animated: true, completion: nil)
This is the second code I have tried. The problem is that with this code, the navigation bar doesn't show at all in the second scene.
I have tried another way, but instead of second scene, the screen just turned black.
Calling present will present the new view controller modally over the current view controller.
What you want is to push the new view controller onto the existing UINavigationController with pushViewController .
if let savedVC = storyboard?.instantiateViewController(withIdentifier: "savedMode") as? SavedModeViewController,
let navigationController = self.navigationController {
navigationController.pushViewController(savedVC, animated: true)
} else {
// TODO: Handle unexpected nil occurrences
print("Error loading SavedModeViewController")
}
I am facing a interrogation, and I hope you could help me to find an answer.
I am selecting X items in a row.
I have already made a custom alert for 1 item selection in a row that look like this.
Here is how I call the alert
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "uploadAlert") as! AlertUploadViewController
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
let image = self.interventionsSelected[0].picture
myAlert.initAlert(image: image!)
request.view.present(myAlert, animated: true, completion: nil)
As you can see, I'm only taking the first image selected. Now, I want to take ALL selected images.
My problematic here is that I want to link all alerts and make smooth transition between them. How can I link the dismissing of an alert and the presenting of the next one ?
Should I make a array of all alerts and catch the dismissing ?
Should I have to use closure and linking all closure ?
I am not sure how I can do that in the best possible smooth way.
you need to maintain a delegate method for the alertcontroller of yours
make a Protocol as
protocol AlertProtocol: class {
func showNextAlert()
}
now assign your main view controller as its delegate
in your alertviewcotntroller:
var delegate: AlertProtocol?
and as you click button in alert controller to do something and close the alert , in its completion handler
self.dismiss(animated: true) {
self.delegate.showNextAlert()
}
this was your current alert closes, and a window heirarcy is maintained and another alert is opened with new data in flow
I would not go with multiple alerts at the same time because it seems visually what will happen is your darker background will be even darker with more alerts. And when dismissing each of them you will have the background brighter...
Supporting multiple images internally seems quite good but it is maybe not scalable if you later choose that some of these internal views will be completely different (one for images the other for whatever you can think of).
The best scenario I can think of is creating this overlay view controller with a container view. So this view is capable of showing view controllers and is able to animate them internally. The interface from how you then use it would look something like:
let container = MyContainerViewController.instantiateFromStoryboard()
container.injectViewControllers([
ShowImageViewController.instantiateFromStoryboard(image: image[0], delegate: self),
ShowImageViewController.instantiateFromStoryboard(image: image[1], delegate: self),
ShowSomethingElseViewController.instantiateFromStoryboard(data: data[0], delegate: self)
])
present(container...
So then I guess you would have a delegate method such as
func embeddedController(_ sender: UIViewController, shouldProceedWithData outputData: Any?) {
if container.canGoForward() {
container.forward(animated: true)
} else {
container.dismiss(...
}
}
I would like to process code when a ViewController is no longer visible due to presenting a new ViewController.
I cannot use ViewWillDisappear etc since the controller is not technically ever dismissed from the stack - you just can't see it.
What process can I use so that code runs when the controller is no longer visible (i.e. topmost) and when it becomes visible again?
EDIT:
Seems some confusion here - not sure why.
I have a viewcontroller.
I use the following code to present another controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewControllerWithIdentifier("NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
self.presentViewController(navController, animated: true, completion: nil)
This controller does not trigger a viewWillDisappear on the previous controller since the previous view is not removed - just hidden.
I need to process code when this view is hidden (i.e. not visible) and, more importantly, process code when it becomes visible again.
When presenting a UIViewController if the presentation style has been set to UIModalPresentationOverCurrentContext it doesn't call the viewWillDisappear and related methods as the view never disappears or gets hidden.
A simple test to check if thats the case would be to set the NavController that you are using to have a clear background color. If you do this and present the NavController and you can still view the first UIViewController below your NavController content. Then you are using UIModalPresentationOverCurrentContext and that is why the viewDidDisappear isn't called.
Have a look at the answer referenced by Serghei Catraniuc (https://stackoverflow.com/a/30787112/4539192).
EDIT: This is in Swift 3, you can adjust your method accordingly if you're using an older version of Swift
If you won't be able to figure out why viewDidAppear and viewDidDisappear are not called, here's a workaround
protocol MyControllerDelegate {
func myControllerWillDismiss()
}
class MyController: UIViewController {
var delegate: MyControllerDelegate?
// your controller logic here
func dismiss() { // call this method when you want to dismiss your view controller
// inform delegate on dismiss that you're about to dismiss
delegate?.myControllerWillDismiss()
dismiss(animated: true, completion: nil)
}
}
class PresentingController: UIViewController, MyControllerDelegate {
func functionInWhichYouPresentMyController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: "NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
thisController.delegate = self // assign self as delegate
present(navController, animated: true, completion: {
// place your code that you want executed when it disappears here
})
}
func myControllerWillDismiss() {
// this method will be called now when MyController will dismiss
// place your code that you want executed when it re-appears here
}
}
Firstly, thanks to Serghei for his time in helping work through this.
To clarify, both my potential presented controllers were set to Full Screen presentation style in the storyboard, however one was being set to Custom via a piece of pasted code dealing with the presentation. I can't find the error with the other.
However, if I force a presentation style of Full Screen as part of the presenting process then all is ok.
Hopefully my frustrating afternoon can help to save someone else's - always try to understand the implications and processes involved in pasted snippets.
In my App, I've created a new storyboard that serves as a very basic tutorial for how to use certain features. (Instructions.storyboard). This storyboard has it's own class - InstructionsVC.swift
I want to present InstructionsVC when MainVC loads within viewDidAppear.
It works great. Fires up on App load just like it's supposed to. The problem occurs when I press the [Close] button on the Instructions interface. It closes the VC, fades to the main screen, and then immediately fires the Instructions VC back up.
How can I prevent the Instructions VC from loading back up once it's closed?
func openInstructions() {
let storyboard = UIStoryboard(name: "Instructions", bundle: nil)
let instructionsView = storyboard.instantiateViewController(withIdentifier: "instructionsStoryboardID")
instructionsView.modalPresentationStyle = .fullScreen
instructionsView.modalTransitionStyle = .crossDissolve
self.present(instructionsView, animated: true, completion:nil)
}
override func viewDidAppear(_ animated: Bool) {
openInstructions()
}
And within my instructions class, I have the following action on the close button:
#IBAction func closeButtonPressed(_ sender: UIButton) {
let presentingViewController: UIViewController! = self.presentingViewController
presentingViewController.dismiss(animated: true, completion: nil)
}
Note - I'd rather not use UserDefaults to resolve this, because I'm going to be incorporating something similar in other parts of the App and don't want to resort to UserDefaults to achieve the desirable behavior.
Thanks in advance buddies!
viewWillAppear and viewDidAppear are called every time a view controller's content view becomes visible. That includes the first time it's rendered and when it's shown again after being covered by a modal or by another view controller being pushed on top of it in a navigation stack.
viewDidLoad is only called once when a view controller's content view has been loaded, but before it is displayed. Thus when viewDidLoad is called it may be too soon to invoke your second view controller.
You might want to add an instance variable hasBeenDisplayed to your view controller. In viewDidAppear, check hasBeenDisplayed. If it's false, display your second view controller and set hasBeenDisplayed to true.