如何正确加载Xib中的NSView?
我的代码:
var topLevelArray: NSArray? = nil
let outputValue = AutoreleasingUnsafeMutablePointer<NSArray>(&topLevelArray)
if Bundle.main.loadNibNamed("RadioPlayerView", owner: nil, topLevelObjects: outputValue) {
let views = outputValue.pointee
return views.firstObject as! RadioPlayerView
}
topLevelArray = nil
return nil
问题是“outputValue”是一个自动释放指针,一旦我从函数返回,程序就会崩溃,ACCESS_BAD_ADDRESS
答案 0 :(得分:6)
我做了一个协议和扩展来做到这一点:
import Cocoa
protocol NibLoadable {
static var nibName: String? { get }
static func createFromNib(in bundle: Bundle) -> Self?
}
extension NibLoadable where Self: NSView {
static var nibName: String? {
return String(describing: Self.self)
}
static func createFromNib(in bundle: Bundle = Bundle.main) -> Self? {
guard let nibName = nibName else { return nil }
var topLevelArray: NSArray? = nil
bundle.loadNibNamed(NSNib.Name(nibName), owner: self, topLevelObjects: &topLevelArray)
guard let results = topLevelArray else { return nil }
let views = Array<Any>(results).filter { $0 is Self }
return views.last as? Self
}
}
用法:
final class MyView: NSView, NibLoadable {
// ...
}
//创建名为MyView.xib
// ......其他地方:
let myView: MyView? = MyView.createFromNib()
答案 1 :(得分:0)
我用稍微不同的方法解决了这个问题。 Swift 5中的代码。
如果您要创建从NSView
加载到{。 addSubview和代码中的约束,下面是示例:
.xib
如果没有与类名同名的.xib文件,则代码将仅根据代码创建类。如果有人想以相同的方式从代码和xib文件创建视图,并保持代码井井有条,那么这是一个很好的解决方案(IMO)。
public static func instantiateView<View: NSView>(for type: View.Type = View.self) -> View {
let bundle = Bundle(for: type)
let nibName = String(describing: type)
guard bundle.path(forResource: nibName, ofType: "nib") != nil else {
return View(frame: .zero)
}
var topLevelArray: NSArray?
bundle.loadNibNamed(NSNib.Name(nibName), owner: nil, topLevelObjects: &topLevelArray)
guard let results = topLevelArray as? [Any],
let foundedView = results.last(where: {$0 is Self}),
let view = foundedView as? View else {
fatalError("NIB with name \"\(nibName)\" does not exist.")
}
return view
}
public func instantiateView() -> NSView {
guard subviews.isEmpty else {
return self
}
let loadedView = NSView.instantiateView(for: type(of: self))
loadedView.frame = frame
loadedView.autoresizingMask = autoresizingMask
loadedView.translatesAutoresizingMaskIntoConstraints = translatesAutoresizingMaskIntoConstraints
loadedView.addConstraints(constraints.compactMap { ctr -> NSLayoutConstraint? in
guard let srcFirstItem = ctr.firstItem as? NSView else {
return nil
}
let dstFirstItem = srcFirstItem == self ? loadedView : srcFirstItem
let srcSecondItem = ctr.secondItem as? NSView
let dstSecondItem = srcSecondItem == self ? loadedView : srcSecondItem
return NSLayoutConstraint(item: dstFirstItem,
attribute: ctr.firstAttribute,
relatedBy: ctr.relation,
toItem: dstSecondItem,
attribute: ctr.secondAttribute,
multiplier: ctr.multiplier,
constant: ctr.constant)
})
return loadedView
}
文件名和类名必须具有相同的名称:
在.xib
文件中,您应该只有一个视图对象,并且该对象必须具有设置的类:
您需要在类代码中添加的只是.xib
中的instantiateView()
,例如:
awakeAfter
例如要实例化此视图您可以使用ViewController创建这样的视图:
import Cocoa
internal class ExampleView: NSView {
internal override func awakeAfter(using coder: NSCoder) -> Any? {
return instantiateView() // You need to add this line to load view
}
internal override func awakeFromNib() {
super.awakeFromNib()
initialization()
}
}
extension ExampleView {
private func initialization() {
// Preapre view after view did load (all IBOutlets are connected)
}
}
或
let exampleView: ExampleView = .instantiateView()
但是Swift有时会遇到这样的实例化问题:
let exampleView: ExampleView = ExampleView.instantiateView()
在控制器的let exampleView = ExampleView.instantiateView()
中,您可以将此视图添加为子视图:
viewDidLoad()