无法添加从 XIB 加载的 UIView 作为 ViewController 视图的子视图
Can't add a UIView loaded from XIB as a subView of a ViewController view
我想在我的应用程序中实现数据更新成功与否时的弹出通知。为此,我:
创建了一个 .xib 文件:Screenshot
创建了一个 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)
}
在上面的 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 种方式获取其框架:
- 直接来自 XIB
- 通过在编程初始化期间提供框架
- 从故事板中创建视图时的框架集
为了使视图适用于所有这些场景,我们不应该在自定义视图内部进行任何框架调整,而将其留给父视图/容器/父视图
所以我做了以下更改以适用于所有场景:
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)
}
来自故事板
这给出了以下结果
我想在我的应用程序中实现数据更新成功与否时的弹出通知。为此,我:
创建了一个 .xib 文件:Screenshot
创建了一个 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) }
在上面的 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 种方式获取其框架:
- 直接来自 XIB
- 通过在编程初始化期间提供框架
- 从故事板中创建视图时的框架集
为了使视图适用于所有这些场景,我们不应该在自定义视图内部进行任何框架调整,而将其留给父视图/容器/父视图
所以我做了以下更改以适用于所有场景:
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)
}
来自故事板
这给出了以下结果