Presenting Modal UINavigationController Changes Background of presented UIViewController - swift

I have a current controller within a UITabBarController
I present a UINavigationController on this UITabBarController with a modal style of .overFullScreen
I then want to present modally another controller on top of this however, the background color of the second modally presented controller is grey. Even if it's set to another color it will revert to a grey color.
How do I remove this color issue?
CODE
// MARK: - WITHIN TABBARCONTROLLER
fileprivate func presentCreatePostController() {
let controller = CreatePostController()
let navigationController = UINavigationController(rootViewController: controller)
navigationController.navigationBar.configureAppearance()
navigationController.modalPresentationStyle = .overFullScreen
navigationController.navigationBar.isTranslucent = true
selectedViewController?.present(navigationController, animated: true)
}
// MARK: - WITHIN CREATEPOSTCONTROLLER
func uploadMedia() {
print("UPLOAD IMAGE BUTTON TAPPED")
PermissionManager.shared.requestPhotoLibraryPermission()
let navigationController = UINavigationController(rootViewController: ControllerProvider.mediaPickerController)
navigationController.modalPresentationStyle = .popover
navigationController.definesPresentationContext = true
let rootViewController: UIViewController = (UIApplication.shared.windows.last?.rootViewController)!
self.view.endEditing(true)
rootViewController.present(navigationController, animated: true, completion: nil)
}

Related

Swift: How to access second view after navigation controller?

I'm really new to swift and currently working on a project for a trivia app. Right now I have it so that after pressing the start button, it pushes aside the current view controller and shows the user a new one (vc), but I'm not sure how/where to add textfields and buttons to that new view (vc) that the user sees after pressing start.
let vc = UIViewController()
vc.view.backgroundColor = .red
navigationController?.pushViewController(vc, animated: true)
You have to create another view controller with UI in it.
class SecondViewController: UIViewController {
private let textView = UITextView()
func viewDidLoad() {
super.viewDidLoad()
// Add text view with auto layout
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
textView.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
}
Then when trying to push a new view controller use this SecondViewController
let vc = SecondViewController()
vc.view.backgroundColor = .red
navigationController?.pushViewController(vc, animated: true)
you can declare view controller with the specified identifier
let vc = UIStoryboard.init(name: "Authentication", bundle: Bundle.main).instantiateViewController(withIdentifier: "Login") as? LoginVC
self.navigationController?.pushViewController(vc!, animated: true)

error instantiating a viewcontrolelr modally via code

why with this code, outlets in second view controller are unwrapped as the were nil crashing the app? they even are not appearing. issue happens if I try to access the outlets, but not if I change the view's background.
in view controller 1 button:
let vc = SecondViewController.self.createAcertainCustomAppearenceOfVC()
vc.modalPresentationStyle = .overCurrentContext
vc.modalTransitionStyle = .crossDissolve
present(vc, animated: true, completion: nil)
in second view controller
final class func createAcertainCustomAppearenceOfVC() -> SecondViewController {
let VC = SecondViewController()
VC.view.backgroundColor = .systemRed
// VC.tappedSecondOut.setTitle("push", for: .normal)
VC.tappedSecondOut.backgroundColor = .black
return VC
}
You're trying to instantiate a view controller that has outlets in a storyboard, you need to instantiate the controller from both the storyboard name and the view controller's identifier that is set in Interface Builder.
final class func createAcertainCustomAppearenceOfVC() -> SecondViewController? {
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
return storyboard.instantiateViewController(withIdentifier: "secondViewControllerIdentifier") as? SecondViewController
}

How to set title of navigation item after open another view controller using with present modally - Swift

I have two view controllers in my application. Root view controller has some components and one of them is a button. That button provides to open another view controller using with present function. Second view controller(SelectTimeViewController) that opens when the button is tapped, was opened successfully. I am trying to set navigation title and items but I can not see them.
I did not use storyboard so root view controller is setting from AppDelegate.
let viewController = ViewController()
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController(rootViewController: viewController)
window?.makeKeyAndVisible()
When tapped the button, "openVC" function is invoked.
#IBAction func openVC(_ sender: Any) {
self.navigationController?.present(SelectTimeViewController(), animated: true, completion: nil)
}
I am trying to set title and rightBarButtonItem in SelectTimeViewController's viewDidLoad function but I can not see both of them.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "close"), style: .plain, target: self, action: #selector(closeIt))
self.navigationItem.title = "Select Time"
}
In additional to this, I can see both title and right bar button item when change the "openVC" function like as bellow.
#IBAction func openVC(_ sender: Any) {
self.navigationController?.pushViewController(vc, animated: true)
}
You have to push the SelectTimeViewController instead of present
Or if you really want to present it, you should present another UINavigationController that has the rootViewController as SelectTimeViewController()

dimiss modal and return to presented childViewController in containerView

I am having a bit of an issue with dismissing a modal view presented from a childviewController in a container view. I have a UINavigationController as the rootViewController (MainNavigationController), and present a modal from one of the childViewControllers from the selectedSegmentIndex 1 (secondViewController). The modal is presented fine, but when I dismiss the modal to go back to the secondViewController(a subclass of HomeController) it returns me back to selectedIndex 0, so not the selectedIndex 1 childViewController it was presented from. I would like the modal to dismiss and return the user back to childViewController it was presented from (the secondViewController) and not return back to selectedIndex 0. Thanks in advance!
// NavigationConroller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// HomeController as parentViewController
class HomeController: UIViewController, FBSDKLoginButtonDelegate {
// child view controllers to put inside content view
var firstViewController: TravelersFeedVC?
var secondViewController: ProfileVC?
private var activeViewController: UIViewController? {
didSet {
removeInactiveViewController(inactiveViewController: oldValue)
updateActiveViewController()
}
}
private func removeInactiveViewController(inactiveViewController: UIViewController?) {
if let inActiveVC = inactiveViewController {
// call before removing child view controller's view from hierarchy
inActiveVC.willMove(toParentViewController: nil)
inActiveVC.view.removeFromSuperview()
// call after removing child view controller's view from hierarchy
inActiveVC.removeFromParentViewController()
}
}
private func updateActiveViewController() {
if let activeVC = activeViewController {
// call before adding child view controller's view as subview
addChildViewController(activeVC)
activeVC.view.frame = contentView.bounds
contentView.addSubview(activeVC.view)
// call before adding child view controller's view as subview
activeVC.didMove(toParentViewController: self)
}
}
// UI elements
lazy var contentView: UIView = {
let tv = UIView()
tv.backgroundColor = UIColor.purple
tv.translatesAutoresizingMaskIntoConstraints = false
tv.layer.masksToBounds = true
return tv
}()
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
activeViewController = firstViewController
checkIfUserIsLoggedIn()
view.addSubview(contentView)
setupProfileScreen()
let items = ["Travelers", "Me"]
segmentedController = UISegmentedControl(items: items)
navigationItem.titleView = segmentedController
segmentedController.tintColor = UIColor.black
segmentedController.selectedSegmentIndex = 0
// Add function to handle Value Changed events
segmentedController.addTarget(self, action: #selector(HomeController.segmentedValueChanged(_:)), for: .valueChanged)
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(handleSignOut))
navigationItem.leftBarButtonItem?.tintColor = UIColor.black
}
// reference to collectionViewController
var travelersFeedVC: TravelersFeedVC!
func segmentedValueChanged(_ sender:UISegmentedControl!)
{
switch segmentedController.selectedSegmentIndex {
case 0:
activeViewController = firstViewController
case 1:
activeViewController = secondViewController
default: // Do nothing
break
}
}
// secondViewcontroller in containerView where modal is presented from
class ProfileVC: UIViewController {
// button to present modal
lazy var placesButton: UIButton = {
let customButton = UIButton(type: .system)
customButton.backgroundColor = UIColor.clear
// customButton.frame = CGRect(x: 150, y: 50, width: 120, height: self.view.frame.height)
customButton.setTitle("## of Places", for: .normal)
customButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
customButton.setTitleColor(.white, for: .normal)
customButton.addTarget(self, action: #selector(handleShowPlacesVC), for: .touchUpInside)
return customButton
}()
// function to call to present modal
func handleShowPlacesVC() {
let placesVC = PlacesTableVC()
let navigationController = UINavigationController(rootViewController: placesVC)
present(navigationController, animated: true, completion: nil)
}
// modal view to dismiss
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "back", style: .plain, target: self, action: #selector(handleCancel))
}
// dismiss modal view to return to secondViewController in childViewController containerView
func handleCancel() {
dismiss(animated: true, completion: nil)
}
When closing the modal dialog the viewDidAppear function in MainNavigationController is called. There you set a new homeController with it's childs. This will trigger a viewDidload in the HomeController with setting of firstViewController. Try to set a breakpoint there and you will see it.
I suggest to avoid content creation in viewDidAppear, use viewDidLoad instead.
Another hint: 'dismiss' is defined as: 'Dismisses the view controller that was presented modally by the view controller.' - If you open for instance an alert above your modal vc it closes the alert, not the modal view (self). A correct implementation has to call dismiss on the presenting controller (same controller that opened it): "presentingViewController?.dismiss()"
It works in your code because apple has implemented a fallback for the case that nothing is presented, but it's a trap that cause some headache sometime.
The chances are that although you're calling present from the child view controller, it isn't in fact handling the presentation. From the Apple docs:
The object on which you call this method may not always be the one that handles the presentation. Each presentation style has different rules governing its behavior. For example, a full-screen presentation must be made by a view controller that itself covers the entire screen. If the current view controller is unable to fulfill a request, it forwards the request up the view controller hierarchy to its nearest parent, which can then handle or forward the request.
Since you're keeping a reference of the active view controller, one solution may be to explicitly set the index upon dismissal.

show segue from modal viewController

I am trying to implement a push/show segue from a UITableViewController that is presented modally. I am able to perform a push segue from a row in the tableview, but it is delayed. I know it has something to do with the navigation hierarchy stack, and I have tried many things like resetting the rootViewController but am not having any luck...thanks for any help!
// present place tableViewController ---> modally
func handleShowPlacesVC() {
let vc = PlacesTableVC()
let navigationController = UINavigationController(rootViewController: vc)
present(navigationController, animated: true, completion: nil)
}
// show details tableViewController ---> push segue from tableview
func handleShowCurrentUserPlaceDetailsVC() {
let vc = CurrentUserPlaceDetailsVC()
navigationController?.pushViewController(vc, animated: true)
}