multiverse
multiverse

Reputation: 483

Failing to access target viewController

I want to have a blur effect on my targetController, i code for it in my view controller , as suggested by one expert @DonMag , but the blur effect does not show and a button to close the view does not respond to click , the images of what i have and what i want are below

enter image description here

enter image description here

My viewcontroller code

import UIKit

class RateMainViewCell: UITableViewCell {
    
    var btnCollection: [UIButton] = []
    var bgImage = UIImageView()
    var crossBtn = UIButton()
    
    
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        // Configure the view for the selected state
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func commonInit() {
        
        let coolBtn = UIButton()
        coolBtn.setTitle("cool", for: .normal)
        coolBtn.setImage(UIImage(named: "cool"), for: .normal)
        
        let sadBtn = UIButton()
        sadBtn.setTitle("sad", for: .normal)
        sadBtn.setImage(UIImage(named: "sad"), for: .normal)
        
        let angryBtn = UIButton()
        angryBtn.setTitle("angry", for: .normal)
        angryBtn.setImage(UIImage(named:"angry"), for: .normal)
        
        
        let loveBtn = UIButton()
        loveBtn.setTitle("love", for: .normal)
        loveBtn.setImage(UIImage(named:"love"), for: .normal)
        
        let happyBtn = UIButton()
        happyBtn.setTitle("happy", for: .normal)
        happyBtn.setImage(UIImage(named:"happy"), for: .normal)
        
        btnCollection.append(contentsOf: [happyBtn,coolBtn,loveBtn,sadBtn,angryBtn])
        
        let font = UIFont(name: "Rubik-Medium", size: 30)
        let fontM = UIFontMetrics(forTextStyle: .body)
        
        
        
        bgImage.translatesAutoresizingMaskIntoConstraints = false
        bgImage.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
        bgImage.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
        
        bgImage.contentMode = .scaleAspectFill
        
        let animationBlur = UIBlurEffect(style: .dark)
        let visualBlur = UIVisualEffectView(effect: animationBlur)
        visualBlur.frame = bgImage.bounds
        
        addSubview(visualBlur)
        
        let stackMoji = UIStackView()
        
        stackMoji.axis = .vertical
        stackMoji.spacing = 10
        stackMoji.alignment = .top
        stackMoji.distribution = .fill
        stackMoji.translatesAutoresizingMaskIntoConstraints = false
        
        
        btnCollection.forEach { (stackBtn) in
            stackMoji.addArrangedSubview(stackBtn)
            
            stackBtn.titleLabel?.font = fontM.scaledFont(for: font!)
        }
        
        bgImage.addSubview(stackMoji)
        
        crossBtn.setImage(UIImage(named: "cross"), for: .normal)
        
        bgImage.addSubview(crossBtn)
        crossBtn.translatesAutoresizingMaskIntoConstraints = false
        crossBtn.trailingAnchor.constraint(equalTo: bgImage.leadingAnchor, constant: 30).isActive = true
        crossBtn.topAnchor.constraint(equalTo: bgImage.topAnchor, constant: 50).isActive = true
        
        
        
        
        
        stackMoji.centerXAnchor.constraint(equalTo: bgImage.centerXAnchor).isActive = true
        stackMoji.centerYAnchor.constraint(equalTo: bgImage.centerYAnchor).isActive = true
        
    }
    
}

The code of controller where i want all this to show up

import UIKit

class RateController: UIViewController {
    var restaurant: Restaurant!
    let rateViews = RateMainViewCell()
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(rateViews.bgImage)
        view.addSubview(rateViews.crossBtn)
        
        if let restaurantImage = restaurant.image {
            
            rateViews.bgImage.image = UIImage(data: restaurantImage as Data)
            
        }
        
        
        
        rateViews.crossBtn.addTarget(self, action: #selector(closeRatings), for: .touchUpInside)

        // Do any additional setup after loading the view.
    }
    
    @objc func closeRatings() {
      
        navigationController?.popViewController(animated: true)
    }
    
    
    /*
     // MARK: - Navigation
     
     // In a storyboard-based application, you will often want to do a little preparation before navigation
     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     // Get the new view controller using segue.destination.
     // Pass the selected object to the new view controller.
     }
     */
    
    override func viewWillAppear(_ animated: Bool) {
        
        let backImage = UIImage(named: "back")
        navigationController?.navigationBar.backIndicatorImage = backImage
        navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
        
        navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: self, action: nil)
        
    }
    
}

UPDATE- code to come to ratecontroller

@objc func gotoRatings() {
        let rateDetailView = RateController()
      rateDetailView.restaurant = restaurant
        rateDetailView.modalPresentationStyle = .fullScreen
        present(rateDetailView, animated: true, completion: nil)
                     //     rateDetailView.restaurant = restaurant
                          
                          //navigationController?.pushViewController(rateDetailView, animated: true)
    }

Upvotes: 0

Views: 71

Answers (1)

DonMag
DonMag

Reputation: 77691

If I understand correctly, you:

  • have navigated to a VC in a navigation controller
  • want to tap a button (or some other action) and present a stack of rating buttons
  • want the background to be a dark, blurred image
  • want it full-screen (so it covers the navigation bar)

First, your "X" button cannot be tapped because it's in the safe-area. It needs to be positioned with respect to that.

Second, your visual effects view is not showing because you didn't give it a frame.

Third, absolutely no idea why you would try to use a UITableViewCell subclass, as this is not a cell in a table view.

Fourth, your "rating" view (or view controller) should know as little as possible about anything else. Design your code so it can simply notify the "calling / presenting controller" that a button was selected. Let that controller decide what to do.

To accomplish the Fourth point, you can use a "callback closure." In your "rating" controller class, declare a variable:

var callback: ((Int) -> ())?

When you instantiate that controller, assign the callback like this:

    // instantiate rating controller
    let vc = MyRateViewController()

    // assign callback closure
    vc.callback = { idx in
        print("button in modal controller tapped:", idx)
        // do what you need to do with that information
    }
    

then, in your modal controller class, use something like this in your button action:

callback?(2)

Here is some example code:

class TestRateViewController: UIViewController {

    let ratings: [String] = [
        "happy",
        "cool",
        "love",
        "sad",
        "angry",
    ]
    
    let statLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // for demonstration, add a button
        // when tapped, it will present MyRateViewController (full-screen)
        let b = UIButton()
        b.setTitle("Test Rate View Controller", for: [])
        b.backgroundColor = .red
        
        // add a label to show the result of the rating
        statLabel.textAlignment = .center
        statLabel.text = "Selected Rating:"
        
        b.translatesAutoresizingMaskIntoConstraints = false
        statLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(b)
        view.addSubview(statLabel)
        
        NSLayoutConstraint.activate([
            b.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            b.topAnchor.constraint(equalTo: view.topAnchor, constant: 200.0),
            statLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            statLabel.topAnchor.constraint(equalTo: b.bottomAnchor, constant: 20.0),
        ])
        
        b.addTarget(self, action: #selector(self.didTap(_:)), for: .touchUpInside)
        
    }
    
    @objc func didTap(_ sender: Any?) -> Void {
        
        // instantiate rating controller
        let vc = MyRateViewController()
        
        // assign its .bgImage property
        if let img = UIImage(named: "food") {
            vc.bgImage = img
        }
        
        // assign its ratings string array
        vc.ratings = self.ratings
        
        // we want it to fill the screen?
        vc.modalPresentationStyle = .overFullScreen
        
        // assign callback closure
        vc.callback = { idx in
            self.gotRating(idx)
        }
        
        present(vc, animated: true, completion: nil)
        
    }
    
    func gotRating(_ idx: Int) -> Void {
        
        // if crossBtn was tapped
        if idx == -1 {
            statLabel.text = "Selected Rating: crossBtn tapped"
            // nothing to do
        } else {
            statLabel.text = "Selected Rating: \(idx) \(ratings[idx])"
            // code to save the selected rating
        }
        
        // dismiss the modal view controller
        dismiss(animated: true, completion: nil)
        
    }
    
}

class MyRateViewController: UIViewController {
    
    // set by calling controller
    var bgImage: UIImage?
    var ratings: [String]?
    var callback: ((Int) -> ())?
    
    let stackMoji = UIStackView()
    let crossBtn = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if ratings == nil {
            fatalError("Caller did not set ratings string array!")
        }
        
        // start with system font
        var font = UIFont.systemFont(ofSize: 30, weight: .bold)

        // buttons use "Rubik-Medium" if available
        if let f = UIFont(name: "Rubik-Medium", size: 30) {
            font = f
        }
        let fontM = UIFontMetrics(forTextStyle: .body)
        
        // properties of stack view
        stackMoji.axis = .vertical
        stackMoji.alignment = .leading
        stackMoji.spacing = 10
        stackMoji.distribution = .fillEqually
        
        // create "moji" buttons, set properties, add to stack
        ratings?.forEach { title in
            let b = UIButton()
            b.setTitle(title, for: [])
            if let img = UIImage(named: title) {
                b.setImage(img, for: [])
            }
            b.imageView?.contentMode = .scaleAspectFit
            b.titleLabel?.font = fontM.scaledFont(for: font)
            b.setTitleColor(.white, for: .normal)
            b.setTitleColor(.gray, for: .highlighted)
            // add target for each button
            b.addTarget(self, action: #selector(self.btnTapped(_:)), for: .touchUpInside)
            stackMoji.addArrangedSubview(b)
        }
        
        // create "cross" button
        if let img = UIImage(named: "cross") {
            crossBtn.setImage(img, for: .normal)
        } else {
            crossBtn.setTitle("X", for: [])
            crossBtn.setTitleColor(.white, for: .normal)
            crossBtn.setTitleColor(.gray, for: .highlighted)
            crossBtn.titleLabel?.font = UIFont.systemFont(ofSize: 44, weight: .bold)
        }
        
        let bkgImageView = UIImageView()
        bkgImageView.backgroundColor = .blue
        bkgImageView.contentMode = .scaleAspectFill
        
        if bgImage != nil {
            bkgImageView.image = bgImage
        }
        
        // create blur visual effect view
        let animationBlur = UIBlurEffect(style: .dark)
        let visualBlurView = UIVisualEffectView(effect: animationBlur)
        
        
        // add elements to self
        view.addSubview(bkgImageView)
        view.addSubview(visualBlurView)
        view.addSubview(stackMoji)
        view.addSubview(crossBtn)
        
        [bkgImageView, visualBlurView, stackMoji, crossBtn].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain background image to all 4 sides
            bkgImageView.topAnchor.constraint(equalTo: view.topAnchor),
            bkgImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            bkgImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            bkgImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            // constrain visualBlurView to all 4 sides
            visualBlurView.topAnchor.constraint(equalTo: view.topAnchor),
            visualBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            visualBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            visualBlurView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            // constrain crossBtn to top / inset 16-pts from left (for aesthetics)
            crossBtn.topAnchor.constraint(equalTo: g.topAnchor, constant: 0),
            crossBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16),
            
            // center the stack view
            stackMoji.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stackMoji.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            
            // make stack view 60% of height of self ?
            stackMoji.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.6),
            
        ])
        
        // add target for crossBtn
        crossBtn.addTarget(self, action: #selector(self.btnTapped(_:)), for: .touchUpInside)
        
    }
    
    @objc func btnTapped(_ sender: UIButton) -> Void {
        
        if sender == crossBtn {
            callback?(-1)
        } else {
            guard let idx = stackMoji.arrangedSubviews.firstIndex(of: sender) else {
                // shouldn't happen
                return
            }
            callback?(idx)
        }
        
    }
    
}

Set a TestRateViewController view controller in your navigation controller. It will look like this (iPhone 11):

enter image description here

When you tap the button, you'll get this:

enter image description here

After tapping an "emoji" button (or the X button), the modal controller will be dismissed and the "Selected Rating" label will be updated.


Note: I used a random food image for the background (named "food") and clipped your "emoji" images out of the image in your question (named "happy", "cool", "love", "sad", "angry").

Here are the images I used:

food

happycoollovesadangry

Upvotes: 1

Related Questions