无法添加从 XIB 加载的 UIView 作为 ViewController 视图的子视图

Can't add a UIView loaded from XIB as a subView of a ViewController view

我想在我的应用程序中实现数据更新成功与否时的弹出通知。为此,我:

  1. 创建了一个 .xib 文件:Screenshot

  2. 创建了一个 class 我加载 NIB 的地方:

      import UIKit
    
      class PopupView: UIView {
    
     static let instance = PopupView()
    
     @IBOutlet weak var backgroundView: UIView!
     @IBOutlet weak var popupView: UIVisualEffectView!
     @IBOutlet weak var symbol: UIImageView!
     @IBOutlet weak var titleLabel: UILabel!
     @IBOutlet weak var descriptionLabel: UILabel!
    
    override init(frame: CGRect) {
     super.init(frame: frame)
     Bundle.main.loadNibNamed("PopupView", owner: self)
     popupView.layer.cornerRadius = 20
    }
    
    required init?(coder: NSCoder) {
     fatalError("init(coder:) has not been implemented")
    }
    
    func showPopup(title: String, message: String, symbol: UIImage, on viewController: UIViewController) {
     self.titleLabel.text = title
     self.descriptionLabel.text = message
     self.symbol.image = symbol
    
     guard let targetView = viewController.view else { return }
    
     backgroundView.frame = targetView.bounds
     targetView.addSubview(backgroundView)
    }
    
  3. 在上面的 class 中,我创建了一个 showPopup 方法,其中定义了一个 backgroundView 帧等于 ViewController 边界。

当我按需要调用该方法时 ViewController 我收到 popupView 显示自身然后立即离开屏幕的行为(GIF 中的黑色区域):GIF

您能帮我理解并解决 popupView 超出屏幕的原因,而不仅仅是等于 ViewController 边界吗?

Shawn Frank 回答后的代码

弹出视图 class:

import UIKit

class PopupView: UIView {

@IBOutlet weak var popupView: UIVisualEffectView!
@IBOutlet weak var symbol: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!

override init(frame: CGRect) {
    super.init(frame: frame)
    configure()
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    configure()
}

private func configure() {
    if let views = Bundle.main.loadNibNamed("PopupView", owner: self) {
        guard let view = views.first as? UIView else { return }
        view.frame = bounds
        addSubview(view)
    }
}

func showPopup(title: String, message: String, symbol: UIImage, on viewController: UIViewController) {

    titleLabel.text = title
    descriptionLabel.text = message
    self.symbol.image = symbol
    
    popupView.layer.cornerRadius = 20
    popupView.clipsToBounds = true
    viewController.view.addSubview(self)
    
    UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveLinear) {
        self.popupView.center.y += 40
    } completion: { _ in
        UIView.animate(withDuration: 0.3, delay: 3.0, options: .curveLinear) {
            self.popupView.center.y -= 80
        } completion: { _ in
            self.popupView.removeFromSuperview()
        }
        
    }
}
}

呼入所需 ViewController:

let width = CGFloat(10)
let midX = (self.view.frame.width - width) / 2.0
let frame = CGRect(x: midX, y: 0, width: width, height: 135)
let popup = PopupView(frame: frame)

popup.showPopup(title: "Test", message: "Tested super test", symbol: UIImage(named: "checkmark.circle.fill")!, on: self)

xib 中的约束:Screenshot

当前结果:GIF

这几天XIB和storyboard用得不多所以也得刷刷刷刷一下用这个tutorial

这是自定义 PopupView class,我不想为此使用单例,但这是我个人的喜好

class PopupView: UIView {
    
    private let xibName = "PopupView"
    
    @IBOutlet weak var visualEffectBackground: UIVisualEffectView!
    @IBOutlet weak var titleLabel: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }
    
    private func configure() {
        if let views = Bundle.main.loadNibNamed(xibName, owner: self),
           let popupView = views.first as? UIView {
            popupView.frame = bounds
            addSubview(popupView)
        }
    }
    
    func showPopup(title: String,
                   on viewController: UIViewController) {
    
        guard let targetView = viewController.view else { return }
        
        titleLabel.text = title
        layer.cornerRadius = 20
        clipsToBounds = true
        targetView.addSubview(self)
    }
}

XIB 是这样设置的:

然后在视图控制器中:

@IBAction func didTapPopUp(_ sender: Any) {
    
    // Give your own width, height etc
    let width = CGFloat(180)
    let midX = (view.frame.width - width) / 2.0
    let frame = CGRect(x: midX, y: 100, width: width, height: 80)
    
    let popup = PopupView(frame: frame)
    popup.showPopup(title: "Hello", on: self)
    
}

给我这个结果:

根据 artexhibit (OPs) 评论更新

自定义视图可以通过我能想到的 3 种方式获取其框架:

  1. 直接来自 XIB
  2. 通过在编程初始化期间提供框架
  3. 从故事板中创建视图时的框架集

为了使视图适用于所有这些场景,我们不应该在自定义视图内部进行任何框架调整,而将其留给父视图/容器/父视图

所以我做了以下更改以适用于所有场景:

class PopupView: UIView {
    
    private static let xibName = "PopupView"
    
    @IBOutlet weak var visualEffectBackground: UIVisualEffectView!
    @IBOutlet weak var titleLabel: UILabel!
    
    init() {
        super.init(frame: .zero)
        configure()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }
    
    private func initializeWithNib() {
        
        var popupView = UIView()
        
        if let views = Bundle.main.loadNibNamed(PopupView.xibName,
                                                owner: self),
           let view = views.first as? UIView {
            
            popupView = view
        }
        
        frame = popupView.bounds
        addSubview(popupView)
    }
    
    private func initializeWithFrame() {
        if let views = Bundle.main.loadNibNamed(PopupView.xibName,
                                                owner: self),
           let popupView = views.first as? UIView {
            popupView.frame = bounds
            addSubview(popupView)
        }
    }
    
    private func configure() {
        
        if frame == .zero {
            initializeWithNib()
        }
        else {
            initializeWithFrame()
        }
        
        layer.cornerRadius = 20
        clipsToBounds = true
    }
    
    func showPopup(title: String,
                   on viewController: UIViewController) {
    
        guard let targetView = viewController.view else { return }
        
        print(frame)
        
        titleLabel.text = title
        targetView.addSubview(self)
    }
}

现在它可以灵活地处理所有 3 种情况:

// In code
@IBAction func didTapPopUp(_ sender: Any) {
    
    // Default initializer
    let popup = PopupView()
    var originX = (view.frame.width - popup.frame.width) / 2.0
    popup.frame.origin = CGPoint(x: originX, y: 100)
    popup.showPopup(title: "Hello", on: self)
    
    // Frame initializer
    let popupWidth: CGFloat = 200
    let popupHeight: CGFloat = 100
    originX = (view.frame.width - popupWidth) / 2.0
    let originY = (view.frame.height - popupHeight) / 2.0
    
    let popupFrame = CGRect(x: originX,
                            y: originY,
                            width: popupWidth,
                            height: popupHeight)
    
    let popupTwo = PopupView(frame: popupFrame)
    popupTwo.showPopup(title: "Frames", on: self)
}

来自故事板

这给出了以下结果