0

I am trying to make a custom UIAlertController with dimmed background which is called by tapping a navigation bar right bar button. My ViewController is embedded in NavigationController and i am expecting my view to cover the whole screen. However, the view is layered either with missing frame if i am adding a subview to navigationController

https://i.stack.imgur.com/IYdKM.png

Or ends up with clear navigation controller if i am adding a subview to ViewController directly

https://i.stack.imgur.com/v0kvL.png

Could you please help me with covering the whole screen? Presenting a ViewController instead of UiView workaround is not fitting in my case. Here's the code:

class ToDoViewContoller: UIViewController {

private let toDoListView = ToDoListView()
private let coreDataManager = CoreDataManager()
private let dimmedView = DimmedView()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(toDoListView)
    setupNavigationBar()
    setupConstraints()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    dimmedView.frame = view.bounds
}

private func setupNavigationBar() {
    
    let appearance = UINavigationBarAppearance()
    appearance.backgroundColor = .systemIndigo
    appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.systemBackground]
    appearance.titleTextAttributes = [.foregroundColor: UIColor.systemBackground]
    
    navigationController?.navigationBar.prefersLargeTitles = true
    navigationController?.navigationBar.standardAppearance = appearance
    navigationController?.navigationBar.compactAppearance = appearance
    navigationController?.navigationBar.scrollEdgeAppearance = appearance
    
    navigationItem.rightBarButtonItem = UIBarButtonItem(
        barButtonSystemItem: .add,
        target: self,
        action: #selector(addButtonTapped))
    navigationItem.rightBarButtonItem?.tintColor = .systemBackground
    navigationItem.title = "ToDos"
}

@objc private func addButtonTapped() {
    view.addSubview(dimmedView)
    
}

private func setupConstraints() {
    NSLayoutConstraint.activate([
        
        toDoListView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        toDoListView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
        toDoListView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
        toDoListView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    ])
 }
}
Fabio
  • 5,432
  • 4
  • 22
  • 24

2 Answers2

0

Your problem here is that the dimmedView is behind the navigation bar. I had ran your code and got this

https://i.stack.imgur.com/Vsi6k.png

Solution 1: For all dialog or popup, I would prefer to use UIViewController rather than UIView, and then use present(_:animated:completion:) to present it

Solution 2: You can try add subview method above the navigation bar in How to add View above navigation controller?

0

I simulate your alert UIViews with my fake views because they aren't in your code, I configure my navigation bar with my extension you can find it here: navigationBar Extension. For your goal you can use additional UIViewCintroller like this example:

In your SceneDelegate set your UINavigationController like that:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    window = UIWindow(windowScene: windowScene)
    window?.makeKeyAndVisible()
    let controller = UINavigationController(rootViewController: ViewController())
    window?.rootViewController = controller
}

After that configure you controller:

class ViewController: UIViewController {

let goButton: UIButton = {
    let button = UIButton()
    button.setTitle("Add", for: .normal)
    button.tintColor = .white
    button.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
    button.layer.cornerRadius = 12
    button.clipsToBounds = true
    button.translatesAutoresizingMaskIntoConstraints = false
    
    return button
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white
    
    setupNavigationBar()
}

private func setupNavigationBar() {
    
    let navBGColor = #colorLiteral(red: 0.3461649418, green: 0.3377231956, blue: 0.839216888, alpha: 1)
    configureNavigationBar(largeTitleColor: .white, backgoundColor: navBGColor, tintColor: .white, title: "ToDos", preferredLargeTitle: true) // my extension to configure navBar
    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped))
    navigationItem.rightBarButtonItem?.tintColor = .systemBackground
    
    goButton.backgroundColor = navBGColor
    goButton.addTarget(self, action: #selector(handleAlertDismiss), for: .touchUpInside)
}

// Dismiss your Custom Alert
@objc fileprivate func handleAlertDismiss() {
    self.dismiss(animated: true)
}

@objc private func addButtonTapped() {
    // Instantiate a new UIViewController for the alert
    let alertVC = UIViewController()
    alertVC.modalPresentationStyle = .overCurrentContext // present alertVC OVER current context 
    alertVC.modalTransitionStyle = .crossDissolve // with transition dissolve
    alertVC.view.backgroundColor = UIColor.black.withAlphaComponent(0.3)
    
    // Add the dimmed view to alertVC controller's view
    let dimmedView = UIView()
    dimmedView.translatesAutoresizingMaskIntoConstraints = false
    dimmedView.backgroundColor = .white
    dimmedView.layer.cornerRadius = 14
    
    alertVC.view.addSubview(dimmedView)
    dimmedView.centerXAnchor.constraint(equalTo: alertVC.view.centerXAnchor).isActive = true
    dimmedView.centerYAnchor.constraint(equalTo: alertVC.view.centerYAnchor).isActive = true
    dimmedView.heightAnchor.constraint(equalToConstant: 250).isActive = true
    dimmedView.widthAnchor.constraint(equalToConstant: 230).isActive = true
    
    dimmedView.addSubview(goButton)
    goButton.bottomAnchor.constraint(equalTo: dimmedView.bottomAnchor, constant: -10).isActive = true
    goButton.leadingAnchor.constraint(equalTo: dimmedView.leadingAnchor, constant: 10).isActive = true
    goButton.trailingAnchor.constraint(equalTo: dimmedView.trailingAnchor, constant: -10).isActive = true
    goButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
    
    // Present the your custom Alert UIViewController
    present(alertVC, animated: true, completion: nil)
  }
}

This is the result:

This is an example

When you tap on Add Button, alertVC dismiss itself for now... You can save your CoreData value in the handleAlertDismiss function.

UPDATE based on your comment:

remove closeButtonTap function from your DimmedView, remove closeButton.addTarget(self, action: #selector(closeButtonTap), for: .touchUpInside) from dimmedView. In ToDoViewContoller controller when create UIVievController add call to closeButtonTap function, move out of addButtonTap function constant alertVC and add the closeButtonTap function...

In ToDoViewContoller

let alertVC = UIViewController()

@objc private func addButtonTapped() {
alertVC.modalPresentationStyle = .overCurrentContext
alertVC.modalTransitionStyle = .crossDissolve
alertVC.view.backgroundColor = .black.withAlphaComponent(0.3) // declare here the bg color, remove it from DimmedView
let dimmedView = DimmedView()
dimmedView.translatesAutoresizingMaskIntoConstraints = false

alertVC.view.addSubview(dimmedView)
dimmedView.centerXAnchor.constraint(equalTo: alertVC.view.centerXAnchor).isActive = true
dimmedView.centerYAnchor.constraint(equalTo: alertVC.view.centerYAnchor).isActive = true
dimmedView.heightAnchor.constraint(equalTo: alertVC.view.heightAnchor).isActive = true
dimmedView.widthAnchor.constraint(equalTo: alertVC.view.widthAnchor).isActive = true

dimmedView.closeButton.addTarget(self, action: #selector(closeButtonTap), for: .touchUpInside)

 present(alertVC, animated: true)
}

@objc private func closeButtonTap() {
 alertVC.dismiss(animated: true)
}

In DimmedView class

override init(frame: CGRect) {
super.init(frame: frame)
//backgroundColor = .black.withAlphaComponent(0.3)
translatesAutoresizingMaskIntoConstraints = false
addSubview(customAlertView)
customAlertView.addSubview(label)
customAlertView.addSubview(textField)
customAlertView.addSubview(closeButton)
customAlertView.addSubview(addButton)
customAlertView.addSubview(detailsTextfield)
setUpConstraints()
}

Now when you tap X close button, alertVC go away...

Fabio
  • 5,432
  • 4
  • 22
  • 24
  • Thank you! I am trying to follow the MVVM pattern so i was evading adding new VC to cover the whole screen. I want to keep the action buttons in view's file but i am unable to reach and close the vc from it. Could you please help me with closing the VC from the view file? I've tried using closures, so vc from mainViewController file will be reachable in view file, but failed to do it. View code: https://privatebin.net/?e05fa17553c8ab60#HgQu9jt8ZEPFMVdoFGKmuKFhJ1ivXEAWN4ud73mhg7iJ VC code: https://privatebin.net/?18f8c1f9d0a247f7#FTNFZWc4DVP5aU9qMfTTx4mFV7Ekri24PMDJac64rhuJ – Nikita Chechnev Apr 25 '23 at 14:56