如何在 Xcode 中创建自定义 UIView?
Posted
技术标签:
【中文标题】如何在 Xcode 中创建自定义 UIView?【英文标题】:How do I create a custom UIView in Xcode? 【发布时间】: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)
【问题讨论】:
【参考方案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,但它仍然比我能找到的任何其他解决方案更好地解决了这个问题以上是关于如何在 Xcode 中创建自定义 UIView?的主要内容,如果未能解决你的问题,请参考以下文章
如何在Xcode + Swift 4中创建自定义UIBarButtonItem类?
在 Swift 中创建自定义 UIView 并显示为弹出窗口
如何使用不同的 init 方法在 XIB 中创建自定义视图?