【问题标题】:How to load NSView from Xib with Swift 3如何使用 Swift 3 从 Xib 加载 NSView
【发布时间】:2017-03-08 11:50:58
【问题描述】:

如何从 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 和内存管理。
  • 是的,这是真的。但我不想保持强引用,我只想加载视图并完成。实际上,我已经解决了这些问题。这很简单,只需删除指针变量: let outputValue = AutoreleasingUnsafeMutablePointer(&topLevelArray) 和 Bundle.main.loadNibNamed("RadioPlayerView", owner: nil, topLevelObjects: &topLevelArray)

标签: swift macos load xib nsview


【解决方案1】:

我为此做了一个协议和扩展:

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

【讨论】:

  • 我已经试过你的代码了。 createFromNib 的最后一行总是返回 nil
  • @prabhu - 还没有
  • 如果您在 nibName 默认使用 self 而不是 Self.self,您将获得动态类型,因此您可以将基类声明为符合要求,每个派生视图都有 nib。
【解决方案2】:

我用稍微不同的方法解决了这个问题。 Swift 5 中的代码。

如果您想创建从 .xib 加载的 NSView,例如addSubview 和代码中的约束,这里是示例:

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 文件创建视图,并保持您的代码井井有条,这是非常好的解决方案 (IMO)。

.xib文件名和类名必须同名:

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

你需要在类代码中添加instantiateView() in 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)]
    )
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-27
    • 2017-12-09
    相关资源
    最近更新 更多