Is it possible to switch to another view controller only by turning the device to left/right?
I would try it with:
//LandscapeTabView
override func viewWillLayoutSubviews() {
if UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft || UIDevice.current.orientation == UIDeviceOrientation.landscapeRight {
}
else {
}
But don't know what to fill in that function?
Thanks for helping a rookie!
First: you can subscribe to system notification about device rotating like this
NotificationCenter.default.addObserver(self, selector: #selector(self.orientationChanged), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
Then make function
func orientationChanged() {}
For correct state determining I recommend using this method
UIApplication.shared.statusBarOrientation == .portrait
If its true - portrait, false - landscape
So, depended on state, for example, you can push some vc on landscape and pop it when device is turned back.
For pushing you can easily create an instance of your ViewController like that
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("here_is_vc_id") as! YourViewController
And for pop:
_ = navigationController.pushViewController(vc, animated: true)
Notice: VC you're instantiating must be store at one (and main) storyboard. Also you need to set up an is (where the string "here_is_vc_id" goes) in Identity Inspector in "Storyboard ID" field.
Here you go :)
Try my little effort-
func rotated()
{
if(UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation))
{
print("landscapeMode")
let nextView = self.storyboard?.instantiateViewControllerWithIdentifier("HomeWorkViewController") as! HomeWorkViewController
self.navigationController?.pushViewController(nextView, animated: true)
}
if(UIDeviceOrientationIsPortrait(UIDevice.currentDevice().orientation))
{
print("PortraitMode")
//As you like
}
}
The approaches suggested are valid but the recommended way to react to this kind of changes is using UIContentContainer protocol (iOS8+).
Then you can add a child view controller to your controller and control how it should animate. You can use this as a reference: Implementing a Container View Controller.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
let isPortrait = size == UIScreen.mainScreen().fixedCoordinateSpace.bounds.size
// Add a child view controller if landscape, remove it if portrait...
}
Related
Is there any equivalent in Swift to RACObserve(self, presentingViewController)?
Or any other why to imitate this behaviour?
My issue is that I want to be notified whenever a view controller is "hidden" by another view controller. In objc what I'd do is to check if self.presentingViewController is nil.
Note that in this scenario there's no knowledge of which view controller is presented, so it's impossible to notify from within its viewDidAppear/viewDidDisappear.
As I understand your question: you need to to know which view controller is presented now and you need notification inviewDidAppear/viewDidDisappear.
So we can get this in several way.
The simple way is:
Get information of which is the top ViewController right now.
2.Call this method in your viewDidAppear/viewDidDisappear
Like this :
Get Which is The Top ViewController
func getTopViewController() -> UIViewController? {
if var topVC = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topVC.presentedViewController {
topVC = presentedViewController
return topVC
}
return topVC
}
return nil
}
Call in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
if let top = getTopViewController() {
print("topView Controller name \(top.title)")
top.view.backgroundColor = .red
}
}
Hope it will help you !
Background
I have an app that uses AVFoundation in order to have a custom camera. This happens in the OCRViewController. When I take a picture I send the captured picture to a different view ImagePreviewViewController.
I am using Xcode 10.2.1 (10E1001) with Swift 5
The Goal
What I would like to achieve is to lock the orientation of the ImagePreviewViewController to the original orientation of the image. I already know how to get the orientation of the image but I am not able to lock the orientation of the view.
I get the image rotation as such: let imageOri = capturedImage?.imageOrientation
What did I try?
I tried the accepted answers at and several other sources:
How to lock orientation just for one view controller?
How to lock orientation of one view controller to portrait mode only in Swift
https://www.hackingwithswift.com/example-code/uikit/how-to-lock-a-view-controllers-orientation-using-supportedinterfaceorientations
Reading the documentation at https://developer.apple.com/documentation/uikit/uiviewcontroller#//apple_ref/occ/instm/UIViewController/supportedInterfaceOrientations under Handling View Rotation the following is stated:
I also tried the many suggested solutions while writing this query, however, the majority appears to use the following approach (or a variation of it), and it does not work for me.
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
override func shouldAutorotate() -> Bool{
return false
}
override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return UIInterfaceOrientation.Portrait
}
As of iOS 8, all rotation-related methods are deprecated. Instead, rotations are treated as a change in the size of the view controller’s view and are therefore reported using the viewWillTransition(to:with:) method.
However, I am not sure how to progress from here.
Interesting code snippets
The following method is in my OCRViewController, here I instantiate the ImagePreviewViewController and attach the captured image.
func displayCapturedPhoto(capturedPhoto : UIImage) {
let imagePreviewViewController = storyboard?.instantiateViewController(withIdentifier: "ImagePreviewViewController") as! ImagePreviewViewController
imagePreviewViewController.capturedImage = capturedPhoto
navigationController?.pushViewController(imagePreviewViewController, animated: true)
}
Using the below override function inside my ImagePreviewViewController I am able to detect the orientation of the view controller.
override func viewDidAppear(_ animated: Bool) {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
}
To restrict the rotation of one screen, use this.
In AppDelegate
var restrictRotation = Bool()
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if !restrictRotation {
return .portrait
} else {
return .all
}
}
In your viewcontroller add the function,
func restrictRotation(restrict : Bool) -> Void {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.restrictRotation = restrict
}
In the ViewDidload() method, call the function to disable rotation.
self.restrictRotation(restrict: false)
in viewWillDisappear() method, call the function to enable rotation.
self.restrictRotation(restrict: true)
Basically for this simple game app I have 2 different UIViewControllers called ViewController and PreviewController. PreviewController is opening view with the title screen and a label titled "Start game". When the label is tapped, it initiates a modal view controller (the ViewController class that has all the views for the actual game itself) and calls the "EnterNewGame" method from ViewController that sets up the game. Right now the issue I have is when calling this method, only part of the method seems to be running.
Here is the function in PreviewController that is being initiated upon tap:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
self.present(ViewController(), animated: true, completion: {() -> Void in
ViewController().enterNewGame()
})
}
And here is the EnterNewGame() method from ViewController
func enterNewGame() {
//show suit indicators when starting a new game
bluePlayerSuitsHidden = false
redPlayerSuitsHidden = false
game.blueTurn = true
self.setBackground()
self.cleanUpBoard()
self.createBoard()
self.displayBoard()
self.setSuitIndicators()
self.highlightCards()
playButton.isEnabled = false
}
Right now, when the label is tapped the screen transitions to the modal view controller but only displays a black screen with only one of the game setups (setting a few images on the top of the screen) working properly. I am sure that the EnterNewGame method works properly to actually start the game because I have tested it in isolation, so I think I am just not setting up the modal view controller properly or I have to call the method differently. Any help is appreciated, thanks.
Controller on which you're calling your method ins't the same instance as controller which you're presenting, you need constant (also your code can be simplified by avoiding using self references and writing name of completion parameter with specifing closure's parameter and return type)
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
let controller = ViewController()
present(controller, animated: true) {
controller.enterNewGame()
}
}
Also, you can call this method on some other method inside your certain controller like viewDidLoad, viewWillAppear or you can create factory method which would return you certain set controller.
This last part leads me to idea: look how you instantiate your controller and look carefully if you don't need to instantiate it through storyboard or nib file.
class ViewController: UIViewController {
class func instantiate() -> ViewController {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Identifier") as! ViewController
// let controller = ViewController(nibName: "ViewController", bundle: nil)
controller.enterNewGame()
return controller
}
}
Usage:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
present(ViewController.instantiate(), animated: true)
}
I have Tap Bar Controller with 2 paths. One is settingController Other one is loginController and contactListController.
When i run the program the entry pint is set tocontactListControllerand if login is false apps shownloginController`. After login value is set on true and loginController is dismiss. On bottom i have Tab Bar Controller: ContactList | Settings
When i go to settings i have a LOGOUT button, i would like to do when i tap this how to set value login on false ? i have no segue between ContactList and SettingController
This is my ContactListController
class ContactsTableViewController: UITableViewController, SettingsControllerDelegate {
let settingsController: SettingsController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("settingsController") as! SettingsController
override func viewDidLoad() {
super.viewDidLoad()
settingsController.delegate = self
}
func didLogoutSuccessfully() {
loggedIn = false
}
}
This is settings controller
protocol SettingsControllerDelegate {
func didLogoutSuccessfully()
}
class SettingsController: UITableViewController {
var delegate: SettingsControllerDelegate?
fun tapButton() {
self.delegate?.didLogoutSuccessfully() // Set login as false
}
if i added
override func viewDidLoad() {
super.viewDidLoad(
settingsController.delegate = self
presentViewController(settingsController, animated: true, completion: nil)
}
my controller setting is appear first. How can i change this value in other way?
UPDATE
in contact list i have
var loggedIn: Bool = false {
didSet {
if loggedIn == true {
self.configureView()
}
}
}
override func viewDidAppear(animated: Bool) {
if loggedIn == false {
performSegueWithIdentifier("showLogin", sender: nil)
}
//tableView.reloadData()
}
Consider either 1: using notifications (to which all interested controllers are registered as observers) to react to session state changes, 2: moving your session state to something "higher up the chain" (like in or "hanging off of" your app delegate), or 3: making a singleton session controller.
1 can be used with 2 and 3 and either 2 or 3 make accessing the current state from anywhere in your app easier. I'd go with a mix of 1 and 3 myself.
This approach in general relieves you from having to walk and inspect the controller hierarchy to find and set the same thig on all other controllers (which is icky because it's so tightly coupled; changing the hierarchy and/or reusing VCs elsewhere would probably break things).
You can pass data between the tabs using UITabViewControllers.viewControllers method which returns array of the view controllers in the tab
//in SettingsVC
func viewWillDisapear(){
//assuming its in the second index of tabBar
let contactVC = self.tabBarController.viewControllers[1] as ContactsTableViewController
contactVC.delegate = self
contactVC.loggedIn = true //or false as you wish
super.viewWillDisapear()
}
I am getting "unexpectedly found nil while unwrapping an Optional value" because in my code below I am trying to assign value to webview before its initialize. I am trying to transition from Master to Detail view controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
let controller = (segue.destinationViewController as UINavigationController).topViewController as DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Detail View Code:
var detailItem: AnyObject? {
didSet {
// Update the view.
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let detailContent = detailItem?.valueForKey("content") as? String{
self.webView.loadHTMLString(detailContent as String, baseURL:nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
It is failing because my Webview in Nil. How do I come around this situation where my outlets are not initialized while setting them.
Please help.
Thanks.
Stop and think about the order in which things happen:
prepareForSegue - The destination view controller exists, but that's all. It has no view and its outlets have not been set. You can set its non-outlet properties but that's all you can do.
The segue starts to happen.
The destination view controller gets viewDidLoad. Now it has a view and its outlets are set.
The segue completes and the destination view controller gets viewWillAppear: and later, viewDidAppear:. Now its view is actually in the interface.
So clearly you cannot permit configureView to assume that the web view exists, because the first time it is called, namely in prepareForSegue, it doesn't exist. configureView needs to test explicitly whether self.webView is nil, and if it is, it should do nothing:
func configureView() {
// Update the user interface for the detail item.
if self.webView == nil { return } // no web view, bail out
if let detailContent = detailItem?.valueForKey("content") as? String{
self.webView.loadHTMLString(detailContent as String, baseURL:nil)
}
}
After that, everything will be fine. viewDidLoad will subsequently be called, and configureView will be called again - and this time, both detailItem and the web view exist, so all will be well.