Swift子类UIView

我刚开始使用Swift。 我读过这本书,在这样做的过程中学得更好。

我想要UIView子类,并显示一个像视图的login。 我已经在Objective-C中创build了这个,但是现在我想把它移植到Swift中。 我不使用故事板,所以我在代码中创build了所有的用户界面。

但是第一个问题是我必须实现initWithCoder 。 我给了它一个默认的实现,因为它不会被调用。 现在,当我运行程序时,它崩溃,因为我也要实现initWithFrame 。 现在我得到了这个:

 override init() { super.init() println("Default init") } override init(frame: CGRect) { super.init(frame: frame) println("Frame init") } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) println("Coder init") } 

我的问题是我应该在哪里创build我的文本字段等…如果我从来没有实现框架和编码器我怎么可以“隐藏”呢?

我通常做这样的事情,有点冗长。

 class MyView: UIView { override init(frame: CGRect) { super.init(frame: frame) addBehavior() } convenience init() { self.init(frame: CGRect.zero) } required init(coder aDecoder: NSCoder) { fatalError("This class does not support NSCoding") } func addBehavior() { print("Add all the behavior here") } } let u = MyView(frame: CGRect.zero) let v = MyView() 

(编辑:我已经编辑了我的答案,以便初始化者之间的关系更清晰)

这更简单。

 override init (frame : CGRect) { super.init(frame : frame) // Do what you want. } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 

自定义UIView子类示例

我通常创buildiOS应用程序,而不使用故事板或笔尖。 我会分享一些我学会回答你的问题的技巧。

隐藏不需要的init方法

我的第一个build议是声明一个基UIView隐藏不需要的初始化。 我在“如何隐藏UI子类中的Storyboard和Nib特定的初始化器”的答案中详细讨论了这种方法。 注意:这种方法假定你不会在故事板或笔尖中使用BaseView或其后代,因为它会故意导致应用程序崩溃。

 class BaseView: UIView { // This initializer hides init(frame:) from subclasses init() { super.init(frame: CGRect.zero) } // This attribute hides `init(coder:)` from subclasses @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("NSCoding not supported") } } 

您的自定义UIView子类应该从BaseViewinheritance。 它必须在其初始化程序中调用super.init()。 它不需要实现init(coder:) 。 这在下面的例子中演示。

添加一个UITextField

我为在init方法之外引用的子视图创build存储的属性。 我通常会这样做一个UITextField。 我喜欢在子视图属性的声明中实例化子视图,如下所示: let textField = UITextField()

除非通过调用addSubview(_:)将其添加到自定义视图的子视图列表中,否则UITextField将不可见。 这在下面的例子中演示。

没有自动布局的编程布局

除非您设置其大小和位置,否则UITextField将不可见。 我经常在layoutSubviews方法中做代码布局(不使用自动布局)。 首先调用layoutSubviews() ,每当resize事件发生。 这允许根据CustomView的大小调整布局。 例如,如果CustomView在各种尺寸的iPhone和iPad上显示完整宽度并调整旋转,则需要适应许多初始尺寸并dynamicresize。

您可以在layoutSubviews()引用frame.heightframe.width来获取CustomView的尺寸以供参考。 这在下面的例子中演示。

示例UIView子类

包含UITextField的自定义UIView子类,不需要实现init?(coder:)

 class CustomView: BaseView { let textField = UITextField() override init() { super.init() // configure and add textField as subview textField.placeholder = "placeholder text" textField.font = UIFont.systemFont(ofSize: 12) addSubview(textField) } override func layoutSubviews() { super.layoutSubviews() // Set textField size and position textField.frame.size = CGSize(width: frame.width - 20, height: 30) textField.frame.origin = CGPoint(x: 10, y: 10) } } 

具有自动布局的编程布局

您也可以使用代码中的自动布局来实现布局。 由于我不经常这样做,所以我不会举例说明。 您可以在堆栈溢出和Internet上的其他地方find在代码中实现自动布局的示例。

程序化布局框架

有代码中实现布局的开源框架。 一个我感兴趣的,但没有尝试过的是LayoutKit 。 它由LinkedIn的开发团队编写。 从Github存储库:“LinkedIn创build的LayoutKit,因为我们发现自动布局不足以执行可滚动视图中的复杂视图层次结构。”

为什么把init(coder:) fatalError init(coder:)

当创build一个永远不会在storyboard或者nib中使用的UIView子类的时候,你可能会引入不同的参数和初始化需求的init(coder:)方法无法调用的init(coder:) 。 如果你没有通过一个fatalError失败init(coder :),那么如果偶然地使用了一个storyboard / nib,它可能会导致非常混乱的问题。 致命的错误主张这些意图。

 required init?(coder aDecoder: NSCoder) { fatalError("NSCoding not supported") } 

如果你想在创build子类的时候运行一些代码,不pipe它是在代码中还是在storyboard / nib中创build的,那么你可以做类似下面的事情(基于Jeff Gu Kang的回答 )

 class CustomView: UIView { override init (frame: CGRect) { super.init(frame: frame) initCommon() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initCommon() } func initCommon() { // Your custom initialization code } } 

Swift 4.0,如果你想使用xib文件的视图,那么这是给你的。 我创build了UIView的CustomCalloutView类的子类。 我创build了一个xib文件,然后在IB中select文件所有者,然后selectAttribute inspector set class name to CustomCalloutView,然后在你的类中创build出口。

  import UIKit class CustomCalloutView: UIView { @IBOutlet var viewCallout: UIView! // This is main view @IBOutlet weak var btnCall: UIButton! // subview of viewCallout @IBOutlet weak var btnDirection: UIButton! // subview of viewCallout @IBOutlet weak var btnFavourite: UIButton! // subview of viewCallout // let nibName = "CustomCalloutView" this is name of xib file override init(frame: CGRect) { super.init(frame: frame) nibSetup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) nibSetup() } func nibSetup() { Bundle.main.loadNibNamed(String(describing: CustomCalloutView.self), owner: self, options: nil) guard let contentView = viewCallout else { return } // adding main view // custom your view properties here self.addSubview(contentView) } } 

重要的是你的UIView可以由界面构build器/故事板或代码创build。 我觉得有一个setup方法可以减less重复的设置代码。 例如

 class RedView: UIView { override init (frame: CGRect) { super.init(frame: frame) setup() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! setup() } func setup () { backgroundColor = .red } } 

这里是我通常如何构build我的子类(UIView)的例子。 我有作为variables的内容,所以他们可以访问和调整,也许以后在其他一些class级。 我还展示了如何使用自动布局和添加内容。

例如,在一个ViewController中,我已经在ViewDidLoad()中初始化了这个视图,因为只有当视图可见时才调用它。 然后,我使用这些函数,我在这里addContentToView()然后activateConstraints()来build立内容和设置约束。 如果我以后在一个ViewController中想让我们说一个button的颜色是红色的,那么我只是在那个ViewController的那个特定的函数中做这个。 就像这样: func tweaksome(){ self.customView.someButton.color = UIColor.red}

 class SomeView: UIView { var leading: NSLayoutConstraint! var trailing: NSLayoutConstraint! var bottom: NSLayoutConstraint! var height: NSLayoutConstraint! var someButton: UIButton = { var btn: UIButton = UIButton(type: UIButtonType.system) btn.setImage(UIImage(named: "someImage"), for: .normal) btn.translatesAutoresizingMaskIntoConstraints = false return btn }() var btnLeading: NSLayoutConstraint! var btnBottom: NSLayoutConstraint! var btnTop: NSLayoutConstraint! var btnWidth: NSLayoutConstraint! var textfield: UITextField = { var tf: UITextField = UITextField() tf.adjustsFontSizeToFitWidth = true tf.placeholder = "Cool placeholder" tf.translatesAutoresizingMaskIntoConstraints = false tf.backgroundColor = UIColor.white tf.textColor = UIColor.black return tf }() var txtfieldLeading: NSLayoutConstraint! var txtfieldTrailing: NSLayoutConstraint! var txtfieldCenterY: NSLayoutConstraint! override init(frame: CGRect){ super.init(frame: frame) self.translatesAutoresizingMaskIntoConstraints = false } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) //fatalError("init(coder:) has not been implemented") } /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ func activateConstraints(){ NSLayoutConstraint.activate([self.btnLeading, self.btnBottom, self.btnTop, self.btnWidth]) NSLayoutConstraint.activate([self.txtfieldCenterY, self.txtfieldLeading, self.txtfieldTrailing]) } func addContentToView(){ //setting the sizes self.addSubview(self.userLocationBtn) self.btnLeading = NSLayoutConstraint( item: someButton, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 5.0) self.btnBottom = NSLayoutConstraint( item: someButton, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0) self.btnTop = NSLayoutConstraint( item: someButton, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0) self.btnWidth = NSLayoutConstraint( item: someButton, attribute: .width, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1.0, constant: 0.0) self.addSubview(self.textfield) self.txtfieldLeading = NSLayoutConstraint( item: self.textfield, attribute: .leading, relatedBy: .equal, toItem: someButton, attribute: .trailing, multiplier: 1.0, constant: 5) self.txtfieldTrailing = NSLayoutConstraint( item: self.textfield, attribute: .trailing, relatedBy: .equal, toItem: self.doneButton, attribute: .leading, multiplier: 1.0, constant: -5) self.txtfieldCenterY = NSLayoutConstraint( item: self.textfield, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0.0) } }