Swift子类UIView


95

我想子类化UIView并显示类似视图的登录名。我已经在Objective-C中创建了它,但是现在我想将其移植到Swift。我不使用情节提要,所以我用代码创建了所有UI。

但是第一个问题是我必须执行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")
}

我的问题是我应该在哪里创建文本字段等?如果我从不实现框架和编码器,该如何“隐藏”呢?

Answers:


174

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

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()

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


4
但是addBehavior被调用了两次,因为调用了initFrame并调用了init。如果我的代码运行第一帧init的印刷,那么默认的init
哈艮地

6
好东西,谢谢。CGRectZero我相信不建议使用而不是使用CGRect.zeroRect
罗杰斯先生2015年

57
初始化程序的东西非常复杂。
伊恩·沃伯顿

3
有什么方法可以对自动版面进行此设置吗?相框太过时了。
devios1 '02

8
这不是一个完整的答案。UIView不支持initWithCoding。从笔尖或情节提要加载的任何视图都将调用initWithCoding方法,然后崩溃。
伊恩·德莱尼

17

这更简单。

override init (frame : CGRect) {
    super.init(frame : frame)
    // Do what you want.
}

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

10

自定义UIView子类示例

我通常在不使用故事板或笔尖的情况下创建iOS应用。我将分享一些我学到的技术来回答您的问题。

 

隐藏不需要的init方法

我的第一个建议是声明一个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子类应继承自BaseView。它必须在其初始化程序中调用super.init()。它不需要实现init(coder:)。在下面的示例中对此进行了演示。

 

添加一个UITextField

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

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

 

没有自动布局的程序化布局

除非您设置UITextField的大小和位置,否则它将不可见。我经常在layoutSubviews方法内的代码中进行布局(不使用自动布局)。layoutSubviews()最初在发生调整大小事件时调用。这允许根据CustomView的大小调整布局。例如,如果CustomView在各种尺寸的iPhone和iPad上显示完整宽度并进行旋转调整,则需要容纳许多初始尺寸并动态调整大小。

您可以参考frame.heightframe.widthlayoutSubviews()获得CustomView的尺寸以供参考。在下面的示例中对此进行了演示。

 

示例UIView子类

自定义的UIView子类,其中包含不需要实现的UITextField 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)
    }
}

 

自动布局的程序化布局

您还可以在代码中使用“自动布局”来实现布局。由于我不经常这样做,因此我不会显示示例。您可以在Stack Overflow和Internet的其他地方的代码中找到实现自动布局的示例。

 

程序化布局框架

有一些开放源代码框架可以在代码中实现布局。我感兴趣但尚未尝试过的一个是LayoutKit。它是由开发团队LinkedIn开发的。从Github存储库中:“ LinkedIn创建了LayoutKit,因为我们发现对于可滚动视图中的复杂视图层次结构,自动布局的性能不足。”

 

为什么把fatalErrorinit(coder:)

当创建将永远不会在情节提要或笔尖中使用的UIView子类时,您可能会引入具有不同参数和初始化要求的初始化程序,而该init(coder:)方法无法调用该初始化程序。如果您没有使init(coder :)失败fatalError,则如果意外在情节提要/笔尖中使用它,可能会导致非常混乱的问题。fatalError声明了这些意图。

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

如果要在创建子类时运行某些代码,而不管它是在代码中创建还是在情节提要/笔尖中创建,则可以执行以下操作(基于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
    }
}

和?通过添加fatalError您禁止使用xib文件初始化此视图
Vyachaslav Gerchicov

@VyachaslavGerchicov,我的回答是假设您没有像接受的答案和问题那样使用xibs或情节提要。该问题提示“我不使用情节提要,所以我用代码创建了所有UI”。
Mobile Dan

下次您fatalError在内部编写dealloc方法并告诉我们它不起作用,因为该类应该是单例。如果您更喜欢在代码中创建UI元素,则不应该手动禁止其他所有方式。最后的问题是如何“以编程方式创建没有情节提要的东西”,但未提及xibs / nibs。在我的情况下,我需要以编程方式+ xib创建一个单元格数组并将其传递给DropDownMenuKit该单元,但这种方式行不通,因为该库的作者也禁止xibs。
Vyachaslav Gerchicov

@VyachaslavGerchicov听起来Jeff Gu Kang的答案就是您正在寻找的内容,因为它可以容纳分镜脚本/ XIB
Mobile Dan

1
@VyachaslavGerchicov这个问题还说:“如果我从不实现框架和编码器,该如何“隐藏”呢?” 当创建将不会在Xib / Storyboard中使用的UIView子类时,您可能会引入具有不同参数的初始化器,而init(coder :)方法无法调用这些参数。如果您没有通过致命错误使init(coder :)失败,那么如果意外地在Xib / Storyboard中使用它,可能会导致非常混乱的问题。fatalError陈述了这些意图。从已接受的答案中可以看出,这是有意的和常见的做法。
Mobile Dan

4

可以通过界面生成器/故事板或通过代码创建UIView,这一点很重要。我发现有一种setup减少重复设置代码的方法很有用。例如

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

4

Swift 4.0,如果您想使用来自xib文件的视图,那么这是给您的。我创建了CustomCalloutView类UIView的Sub类。我创建了一个xib文件,然后在IB中选择文件所有者,然后选择“属性检查器”,将类名称设置为CustomCalloutView,然后在您的类中创建出口。

    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 
            contentView.frame = self.bounds //Comment this line it take default frame of nib view
           // custom your view properties here
            self.addSubview(contentView)
        }
    }

//现在添加

    let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
    self.view.addSubview(viewCustom)

-1

这是我通常如何构建子类(UIView)的示例。我将内容作为变量,以便稍后可以在其他一些类中进行访问和调整。我还展示了如何使用自动布局和添加内容。

例如在ViewController中,我将此视图初始化为ViewDidLoad(),因为只有在该视图可见时才调用一次。然后,我使用在此创建的这些函数 addContentToView(),然后activateConstraints()构建内容并设置约束。如果以后我在ViewController中要让按钮的颜色为红色,则只需在该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)
}
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.