美文网首页
Cocoa Programming for OS X: View

Cocoa Programming for OS X: View

作者: su3 | 来源:发表于2017-02-16 00:57 被阅读0次

    这是 Cocoa Programming for OS X(5e)By Big Nerd Ranch - View Swapping and Custom Container View Controllers 的学习笔记。已将书中的代码更新为 Swift 3 版本。

    Previously

    我们用 NSTabViewController 来让用户切换两个 View Controller,但 NSTabController 是怎么实现的呢?

    View Swapping

    视图切换可以用 addSubview(_:) 和 removeFromSuperView() 方法来实现。

    class NerdBox: NSView {
    
    var contentView: NSView?
    
    func showContentView(view: NSView) {
        contentView?.removeFromSuperview()
        addSubview(view)
        contentView = view
    }
    }
    

    你还需要调整 view 来适应容器,比如添加 auto layout 约束。事实上 Cocoa 提供了一个 NSBox 类来做这个工作。

    NerdTabViewController

    重写 loadView() 方法设置视图。覆盖 child view controller 的管理方法来呼叫它。

    override func loadView() {
        view = NSView()
        reset()
    }
    func reset() {
       
    }
    
    override func insertChildViewController(_ childViewController: NSViewController, at index: Int) {
        super.insertChildViewController(childViewController, at: index)
        if isViewLoaded {
            reset()
        }
    }
    
    override func removeChildViewController(at index: Int) {
        super.removeChildViewController(at: index)
        if isViewLoaded {
            reset()
        }
    }
    

    这个 class 大量个工作将在 reset() 中实现,它将重建视图层级。在实现它之前,添加两个属性和两个方法

    var box = NSBox()
    var buttons: [NSButton] = []
    
    func selectTabAtIndex(index: Int)  {
        assert((0..<childViewControllers.count).contains(index), "index out of range")
        for(i, button) in buttons.enumerated(){
            button.state = (index == i) ? NSOnState : NSOffState
        }
        let viewController = childViewControllers[index]
        box.contentView = viewController.view
    }
    
    func selectTab(sender: NSButton){
        let index = sender.tag
        selectTabAtIndex(index: index)
        
    }
    

    box 用到了 NSBox,它保持所选tab的内容。buttons是一个数组,触发 selectTab(_:) 动作。参数 sender 用来区分那一个tab要显示。

    实现 reset() 重绘视图结构。

    func reset () {
        view.subviews = []
        
        let buttonWidth: CGFloat = 28
        let buttonHeight: CGFloat = 28
        
        let viewControllers = childViewControllers
        buttons = viewControllers.enumerated().map {
            (index, viewController) -> NSButton in
            let button = NSButton()
            button.setButtonType(.toggle)
            button.translatesAutoresizingMaskIntoConstraints = false
            button.isBordered = false
            button.target = self
            button.action = #selector(selectTab(sender:))
            button.tag = index
            button.image = NSImage(named: NSImageNameFlowViewTemplate)
            button.addConstraints([
                NSLayoutConstraint(item: button,
                                   attribute: .width,
                                   relatedBy: .equal,
                                   toItem: nil,
                                   attribute: .notAnAttribute,
                                   multiplier: 1.0,
                                   constant: buttonWidth),
                NSLayoutConstraint(item: button,
                                   attribute: .height,
                                   relatedBy: .equal,
                                   toItem: nil,
                                   attribute: .notAnAttribute,
                                   multiplier: 1.0,
                                   constant: buttonHeight)
                ])
            return button
        }
        
        let stackView = NSStackView()
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.orientation = .horizontal
        stackView.spacing = 4
        for button in buttons {
            stackView.addView(button, in: .center)
        }
        
        box.translatesAutoresizingMaskIntoConstraints = false
        box.borderType = .noBorder
        box.boxType = .custom
        
        let separator = NSBox()
        separator.boxType = .separator
        separator.translatesAutoresizingMaskIntoConstraints = false
        
        view.subviews = [stackView, separator, box]
        
        let views = ["stack": stackView, "separator": separator, "box": box]
        let metrics = ["buttonHeight": buttonHeight]
        
        func addVisualFormatConstraints(visualFormat:String) {
            let constraints =
                NSLayoutConstraint.constraints(withVisualFormat: visualFormat,
                                                               options: [],
                                                               metrics: metrics as [String : NSNumber]?,
                                                               views: views)
            NSLayoutConstraint.activate(constraints)
        }
        addVisualFormatConstraints(visualFormat: "H:|[stack]|")
        addVisualFormatConstraints(visualFormat: "H:|[separator]|")
        addVisualFormatConstraints(visualFormat: "H:|[box(>=100)]|")
        addVisualFormatConstraints(visualFormat: "V:|[stack(buttonHeight)][separator(==1)][box(>=100)]|")
        
        if childViewControllers.count > 0 {
            selectTabAtIndex(index: 0)
        }
        
    }
    

    以上唯一的新概念是 NSStackView,它用于安排多个view的顺序,横向或者纵向,用 gravity 来表示它们将布置在什么位置。这里放置在中间。

    现在你已经实现了 NerdTabViewController,下面在 AppDelegate.swift 应用这个类

    let tabViewController = NerdTabViewController()
    

    Run。目前剩下一个问题是 tab 按钮显示同样的图片。

    添加 Tab 图片

    ImageViewController 中已经有一个 image 属性。但是我们要使 MerdTabViewController 适用于所有的 view controller,我们需要一个方法给任何 view controller,让它提供一个类型安全、可预测的、自成文档的图片。

    Protocol 提供了一个很好的实现这个思路的方法。

    定义一个 protocol

        protocol ImageRepresentable {
            var image: NSImage? { get }
        }
    

    在 reset() 中判断 view controller 是否遵循这个 protocol

            //button.image = NSImage(named: NSImageNameFlowViewTemplate)
            if let viewController = viewController as? ImageRepresentable {
                button.image = viewController.image
            }
            else {
                button.title = viewController.title!
            }
    

    让 ImageViewController 遵循这个 protocol

    class ImageViewController: NSViewController, ImageRepresentable {
    

    因为 ImageViewController 已经有 image 属性了,可以 Run 了。

    相关文章

      网友评论

          本文标题:Cocoa Programming for OS X: View

          本文链接:https://www.haomeiwen.com/subject/jdecwttx.html