如何在Xcode中创建自定义UIView?

时间:2018-06-18 06:41:03

标签: xcode interface-builder

我正在尝试使用UIView上的一些UISegementedControl创建一个相对简单的自定义视图(见下文),但我似乎无法使事情正常工作。

我为视图创建了XIB文件,并将文件所有者设置为自定义类(参见下文)。然后我通过CTRL-Dragging直接从右键单击文件所有者时弹出的面板链接@IBOutlets。

在代码中我还设置了UISegmentedControls的目标,但这些似乎永远不会被调用,同样如果我设置了isSelectionEnabled,分段控件永远不会在视图中被禁用。

我在这里做错了什么?

Custom view

//
//  ScheduleSelectorView.swift
//  
//
//  .
//  
//

import UIKit

enum ScheduleWeeks: String {
    case week1 = "Week 1",
    week2 = "Week 2",
    week3 = "week 3",
    week4 = "week 4"

    static let allValues = [week1,week2,week3,week4]
}
enum ScheduleDays: String {
    case mon = "Mon",
    tue = "Tue",
    wed = "Wed",
    thu = "Thu",
    fri = "Fri",
    sat = "Sat",
    sun = "Sun"

    static let allValues = [mon,tue,wed,thu,fri,sat,sun]
}
enum ScheduleAMPM: String {
    case am = "AM",
    pm = "PM"

    static let allValues = [am, pm]
}
@objc
enum ScheduleSelectorValueType: Int {
    case week, day, hour, minute, ampm, none
}

@objc
protocol ScheduleSelectorDelegate {

    /// called when a value is changed in the selector
    @objc
    func valueChanged(type: ScheduleSelectorValueType, value: Any?)
}

@IBDesignable class ScheduleSelectorView: UIView {

    let nibName = "ScheduleSelectorView"

    @IBOutlet var delegate: ScheduleSelectorDelegate?

    @IBOutlet var contentView: UIView!

    @IBOutlet weak var weekSelector: UISegmentedControl!
    @IBOutlet weak var daySelector: UISegmentedControl!
    @IBOutlet weak var hourSelector: UISegmentedControl!
    @IBOutlet weak var minuteSelector: UISegmentedControl!
    @IBOutlet weak var ampmSelector: UISegmentedControl!

    var selectors: [UISegmentedControl] = []


    @IBInspectable
    var timeInterval: Int = 15 {
        didSet {
            setupMinuteSelector()
        }
    }

    var isSelectionEnabled: Bool = false {
        didSet {
            for selector in selectors {
                DebugLog("isSelectionEnabled: \(isSelectionEnabled): \(selector.numberOfSegments)")
                selector.isUserInteractionEnabled = isSelectionEnabled
            }
        }
    }
    struct Schedule {
        var week:ScheduleWeeks = .week1
        var day: ScheduleDays = .mon
        var hour: Int = 0
        var minute: Int = 0
        var ampm: ScheduleAMPM = .am

        init() {

        }
        init(week: ScheduleWeeks, day: ScheduleDays, hour: Int, minute: Int, ampm: ScheduleAMPM) {
            self.week = week
            self.day = day
            self.hour = hour
            self.minute = minute
            self.ampm = ampm
        }
    }

    var _schedule = Schedule()

    var schedule: Schedule? {
        return _schedule
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
        DebugLog("init(coder)")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
        DebugLog("init(frame)")
    }

    func loadViewFromNib() -> UIView {
        // grabs the appropriate bundle
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
        return view
    }
    func commonInit() {
        Bundle.main.loadNibNamed(nibName, owner: self, options: nil)
        addSubview(contentView)
        contentView.frame = self.bounds
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        selectors = [weekSelector, daySelector, hourSelector, minuteSelector, ampmSelector]
        setup()
    }

    func setup(){
        setupWeekSelector()
        setupDaySelector()
        setupHourSelector()
        setupMinuteSelector()

        // Set targets for changes
        self.weekSelector.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged )
        self.daySelector.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged )
        self.hourSelector.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged )
        self.minuteSelector.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged )
        self.ampmSelector.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged )
    }
    func setupWeekSelector(){
        DebugLog("setupWeekSelector")
        weekSelector.removeAllSegments()

        for (index, title) in ScheduleWeeks.allValues.enumerated() {
            weekSelector.insertSegment(withTitle: title.rawValue, at: index, animated: true)
        }
    }
    func setupDaySelector(){
        daySelector.removeAllSegments()

        for (index, title) in ScheduleDays.allValues.enumerated() {
            daySelector.insertSegment(withTitle: title.rawValue, at: index, animated: true)
        }
    }
    func setupHourSelector(){
        hourSelector.removeAllSegments()

        for t in 0..<12 {
            hourSelector.insertSegment(withTitle: "\(t)", at: t, animated: true)
        }
    }
    func setupMinuteSelector(){
        minuteSelector?.removeAllSegments()

        for t in stride(from: 0, through: 60, by: timeInterval) {
            minuteSelector?.insertSegment(withTitle: "\(t)", at: t, animated: true)
        }
    }
    @objc func selectionDidChange(_ selector: UISegmentedControl) {

        var type: ScheduleSelectorValueType = .none
        var value: Any? = nil
        let index = selector.selectedSegmentIndex

        switch selector {
        case weekSelector:
            type = .week
            value = ScheduleWeeks.allValues[index]
            _schedule.week = ScheduleWeeks.allValues[index]
        case daySelector:
            type = .day
            value = ScheduleDays.allValues[index]
            _schedule.day = ScheduleDays.allValues[index]
        case hourSelector:
            type = .hour
            value = index
            _schedule.hour = index
        case minuteSelector:
            type = .minute
            value = index
            _schedule.minute = index
        case ampmSelector:
            type = .ampm
            value = ScheduleAMPM.allValues[index]
            _schedule.ampm = ScheduleAMPM.allValues[index]
        default:
            type = .none
        }

        self.delegate?.valueChanged(type: type, value: value)
    }
    var valueString: String {
        let week = (weekSelector.selectedSegmentIndex != UISegmentedControlNoSegment) ? weekSelector.selectedSegmentIndex : 0
        let day = (daySelector.selectedSegmentIndex != UISegmentedControlNoSegment) ? daySelector.selectedSegmentIndex : 0
        let hour = (hourSelector.selectedSegmentIndex != UISegmentedControlNoSegment) ? hourSelector.selectedSegmentIndex : 0
        let minute = (minuteSelector.selectedSegmentIndex != UISegmentedControlNoSegment) ? minuteSelector.selectedSegmentIndex : 0
        let ampm = (ampmSelector.selectedSegmentIndex != UISegmentedControlNoSegment) ? ampmSelector.selectedSegmentIndex : 0
        let selection = "\(ScheduleWeeks.allValues[week]), \(ScheduleDays.allValues[day]) \(hour.padded2):\(minute.padded2) \(ScheduleAMPM.allValues[ampm])"
        return selection
    }
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

}

extension UISegmentedControl {

    func setSelectionEnabled(_ enabled: Bool) {

            for i in 0..<self.numberOfSegments {
                self.setEnabled(enabled, forSegmentAt: i)
        }

    }

}

1 个答案:

答案 0 :(得分:1)

我真的很难找到任何实际有效的答案,但最后我设法让它发挥作用。

我认为主要的解决方案来自我在这里找到的NibLoaderView。

iOS - How to initialize custom UIView with specific Frame from NIB

但是我必须做一些改变才能让它在IB中发挥作用。

import UIKit

// Usage: Subclass your UIView from NibLoadView to automatically load a xib with the same name as your class

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        nibSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        nibSetup()
    }
    override open func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        nibSetup()
        view?.prepareForInterfaceBuilder()
    }

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
        view.anchorAllEdgesToSuperview()
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of:self))
        let nib = UINib(nibName: String(describing: type(of:self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }

}

extension UIView {
    func anchorAllEdgesToSuperview() {
        self.translatesAutoresizingMaskIntoConstraints = false
        if #available(iOS 9.0, *) {
            addSuperviewConstraint(constraint: topAnchor.constraint(equalTo: (superview?.topAnchor)!))
            addSuperviewConstraint(constraint: leftAnchor.constraint(equalTo: (superview?.leftAnchor)!))
            addSuperviewConstraint(constraint: bottomAnchor.constraint(equalTo: (superview?.bottomAnchor)!))
            addSuperviewConstraint(constraint: rightAnchor.constraint(equalTo: (superview?.rightAnchor)!))
        }
        else {
            for attribute : NSLayoutAttribute in [.left, .top, .right, .bottom] {
                anchorToSuperview(attribute: attribute)
            }
        }
    }

    func anchorToSuperview(attribute: NSLayoutAttribute) {
        addSuperviewConstraint(constraint: NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .equal, toItem: superview, attribute: attribute, multiplier: 1.0, constant: 0.0))
    }

    func addSuperviewConstraint(constraint: NSLayoutConstraint) {
        superview?.addConstraint(constraint)
    }
}

然后在子类中确保您拥有以下内容:

@IBDesignable class Designable: NibLoaderView {

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

}

我当然希望这能节省一些时间......

如果有兴趣的话,这里有一个可用的示例应用程序

https://ossh.com.au/design-and-technology/software-development/xcode-9-swift-4-ibdesignables-with-xib/

enter image description here