Reputation: 483
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
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
Reputation: 77691
If I understand correctly, you:
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):
When you tap the button, you'll get this:
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:
Upvotes: 1