【问题标题】:How do I create a custom UIView in Xcode?如何在 Xcode 中创建自定义 UIView?
【发布时间】:2018-06-18 06:41:03
【问题描述】:

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

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

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

我在这里做错了什么?

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

    }

}

【问题讨论】:

    标签: xcode interface-builder


    【解决方案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/

    【讨论】:

    • 一个优雅的解决方案。但我认为 nibSetup() 中有几行多余的行。 view.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.translatesAutoresizingMaskIntoConstraints = true 不是必需的,因为无论如何您都可以在 anchorAllEdgesToSuperview() 中撤消此操作。此外,子类中显示的 init() 方法也不是必需的,它们什么都不做。不幸的是,层次结构中还有一个额外的 UIView,但它仍然比我能找到的任何其他解决方案更好地解决了这个问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多