skip to Main Content

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://phpout.com/wp-content/uploads/2023/04/IYdKM.png

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

https://phpout.com/wp-content/uploads/2023/04/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),
    ])
 }
}

2

Answers


  1. 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?

    Login or Signup to reply.
  2. 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…

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search