0

I want to create a custom alert via code. This code below is working, but I Need help on some questions about passing data and retain cycles

  1. is it right my usage of [weak self] ?

  2. I'd like to avoid Delegate Pattern, so my plan is to pass an action as handler, this should keep Controller clean and make code more reusable. Is mine a proper solution?

  3. In my mind, a view should not be "auto removing" but its parent should remove it, so I'd like to pass a reference to the parent controller in order to comunicate via completion, but it seems to create a retain circle (deinit never called), so I'm doing this:

    self?.removeFromSuperview() //works, but not sure it is right
    //self?.parentController.removeFromParent() //never calls deinit
    
  4. I had a problem passing closure as parameter inside an init with cgrect as parameter. Is there a proper way other than this solution to handle that?

    required init(refersTo: UIViewController, comp: @escaping () -> Void) {
             myTransmittedCompletion = comp
             self.parentController = refersTo
             super.init(frame: CGRect.zero)
    }
    

    I call my alert this way

    @IBAction func centralButton(_ sender: UIButton) {
    
         let alert = MyAlertInCode(refersTo: self, comp: testPerCOmpletion)
         self.view.addSubview(alert)
     }
    
    
     func testPerCOmpletion() {
         print("completion")
     }
    

my custom class

class MyAlertInCode: UIView {

let myTransmittedCompletion: () -> Void
let parentController: UIViewController

private let myTitleLabel: UILabel = {
    let v = UILabel()
    v.translatesAutoresizingMaskIntoConstraints = false
    v.numberOfLines = 0
    v.text = "very long long long logn title"
    v.textAlignment = .center
    v.font = UIFont.systemFont(ofSize: 28, weight: .bold)
    return v
}()

private let mySubtTitleLabel: UILabel = {
    let v = UILabel()
    v.translatesAutoresizingMaskIntoConstraints = false
    v.numberOfLines = 0
    v.text = "very long long long logn title"
    v.textAlignment = .center
    v.font = UIFont.systemFont(ofSize: 20, weight: .bold)
    return v
}()

private let myButton: UIButton = {
    let v = UIButton()
    v.translatesAutoresizingMaskIntoConstraints = false
    v.setTitle("Button", for: .normal)
    v.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
    v.setTitleColor(.systemBlue, for: .normal)
    return v
}()

//white panel of the alert
private let container: UIView = {
    let v = UIView()
    v.translatesAutoresizingMaskIntoConstraints = false
    v.backgroundColor = .white
    v.layer.cornerRadius = 24
    v.backgroundColor = .purple
    return v
}()

private lazy var stack: UIStackView = {
    let v = UIStackView(arrangedSubviews: [myTitleLabel, mySubtTitleLabel, myButton])
    v.translatesAutoresizingMaskIntoConstraints = false
    v.axis = .vertical
    v.spacing = 10
    v.distribution = .fillEqually
    v.backgroundColor = .green
    return v
}()






required init(refersTo: UIViewController, comp: @escaping () -> Void) {
    myTransmittedCompletion = comp
    self.parentController = refersTo
    super.init(frame: CGRect.zero)
    
    myButton.addTarget(self, action: #selector(methodInsideAlertClass), for: .touchUpInside)
    
    self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(animateOut)))
    
    self.backgroundColor = UIColor.gray.withAlphaComponent(0.6)
    #warning("UIScreen.main.bounds //deprecated in the future, at 14-ott-2022")
    //        self.frame = UIScreen.main.bounds //deprecated in the future, at 14-ott-2022
    guard let windowBoundsFromIOS13 = UIApplication.shared.currentUIWindow()?.rootViewController?.view.bounds else {return}
    self.frame = windowBoundsFromIOS13
    
    self.addSubview(container)
    
    NSLayoutConstraint.activate([
        container.centerYAnchor.constraint(equalTo: self.centerYAnchor),
        container.centerXAnchor.constraint(equalTo: self.centerXAnchor),
        container.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7),
        container.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.5),
    ])
    
    
    container.addSubview(stack)
    
    NSLayoutConstraint.activate([
        stack.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.6),
        stack.centerYAnchor.constraint(equalTo: container.centerYAnchor),
        stack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
        stack.trailingAnchor.constraint(equalTo: container.trailingAnchor),
    ])
    
    animateIn()
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}


deinit {
    print("❌")
}


//MARK: methods

@objc private func methodInsideAlertClass() {
    print("methodInsideAlertClass tapped")
}

@objc private func animateOut() {
    
    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseIn) {
        self.container.transform = CGAffineTransform(translationX: 0, y: -self.frame.height)
        self.alpha = 0
    } completion: { [weak self] isCompleted in
        if isCompleted {
            self?.myTransmittedCompletion()
            self?.removeFromSuperview() // shouldn't be removed by parent view?
            //                self?.parentController.removeFromParent() //never calls deinit
        }
    }
    
}


@objc private func animateIn() {
    self.container.transform = CGAffineTransform(translationX: 0, y: -self.frame.height)
    self.alpha = 1
    
    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseIn) {
        self.container.transform = .identity
        self.alpha = 1
    }
    
}
}

since cannot use Windows:

public extension UIApplication {
    func currentUIWindow() -> UIWindow? {
        let connectedScenes = UIApplication.shared.connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .compactMap { $0 as? UIWindowScene }
        
        let window = connectedScenes.first?
            .windows
            .first { $0.isKeyWindow }
        
        return window
        
    }
}
biggreentree
  • 1,633
  • 3
  • 20
  • 35

0 Answers0