如何使用Swift 3从Xib加载NSView

3
如何正确地从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。

保持一个强引用使用类属性,那么它就不会被释放。并尝试更好地理解ARC和内存管理。 - Volker
是的,没错。但我不想保留强引用,我只想加载视图并完成。实际上,我已经解决了这些问题。很简单,只需删除指针变量:let outputValue = AutoreleasingUnsafeMutablePointer<NSArray>(&topLevelArray)然后只需要Bundle.main.loadNibNamed("RadioPlayerView", owner: nil, topLevelObjects: &topLevelArray) - Tran Quan
2个回答

20

我制定了一个协议和扩展来实现这个功能:

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 的 xib 文件

// ... 在其他地方:

let myView: MyView? = MyView.createFromNib()

2
我已经尝试了你的代码。createFromNib 方法的最后一行总是返回 nil - Duck
@prabhu - 还没有。 - Duck
如果您在“nibName”默认值中使用“self”而不是“Self.self”,则会得到动态类型,因此您可以声明一个基类作为符合条件,并使每个派生视图都有其自己的nib。 - Giles

0

我用了一种稍微不同的方法来解决这个问题。使用 Swift 5 编写的代码。

如果你想要创建一个从 .xib 加载的 NSView,并且想要通过代码添加子视图和约束,这里有一个示例:

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文件创建视图,并保持代码组织良好,那么这是一个非常好的解决方案(在我看来)。
.xib文件名和类名必须相同:

Xib file name and class name must have the same name

.xib 文件中,您只应该有一个视图对象,并且这个对象必须设置类:

enter image description here

在类代码中,您只需要添加instantiateView()awakeAfter即可,例如:

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)
   }
}

要在例如 ViewController 中实例化此视图,您可以创建以下视图:

let exampleView: ExampleView = .instantiateView() 或者

let exampleView: ExampleView = ExampleView.instantiateView()

但是 Swift 有时会出现这样的实例化问题:

let exampleView = ExampleView.instantiateView()

在您的控制器的 viewDidLoad() 中,您可以将此视图添加为子视图:

internal override func viewDidLoad() {
    super.viewDidLoad()

    exampleView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(exampleView)
    NSLayoutConstraint.activate(
        [exampleView.topAnchor.constraint(equalTo: view.topAnchor),
         exampleView.leftAnchor.constraint(equalTo: view.leftAnchor),
         exampleView.rightAnchor.constraint(equalTo: view.rightAnchor),
         exampleView.bottomAnchor.constraint(equalTo: view.bottomAnchor)]
    )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接