Swift 子类 UIView

Posted

技术标签:

【中文标题】Swift 子类 UIView【英文标题】:Swift subclass UIView 【发布时间】:2014-11-21 08:18:44 【问题描述】:

我想继承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")

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

【问题讨论】:

【参考方案1】:

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

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

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

【讨论】:

但是 addBehavior 被调用了两次,因为 initFrame 被调用并且 init.如果您运行我的代码,第一帧 init 会被打印,那么默认的 init 好东西,谢谢。而不是使用CGRectZero,我相信建议使用CGRect.zeroRect 这个初始化程序非常复杂。 自动布局有什么办法吗?框架太过时了。 这不是一个完整的答案。 UIView 确实支持 initWithCoding。从 nib 或 storyboard 加载的任何视图都会调用 initWithCoding 方法,然后崩溃。【参考方案2】:

这更简单。

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


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

【讨论】:

【参考方案3】:

自定义 UIView 子类示例

我通常在不使用故事板或 nib 的情况下创建 ios 应用程序。我将分享一些我学到的技巧来回答你的问题。

 

隐藏不需要的 init 方法

我的第一个建议是声明一个基 UIView 来隐藏不需要的初始化程序。我在my answer to "How to Hide Storyboard and Nib Specific Initializers in UI Subclasses" 中详细讨论了这种方法。注意:此方法假定您不会在情节提要或 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 这样做。我更喜欢在子视图属性的声明中实例化子视图,如下所示:let textField = UITextField()

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

 

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

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

您可以参考layoutSubviews()内的frame.heightframe.width获取CustomView的尺寸以供参考。这在下面的示例中得到了演示。

 

UIView 子类示例

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

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,因为我们发现自动布局对于可滚动视图中的复杂视图层次结构不够高效。”

 

为什么要把fatalError 放入init(coder:)

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

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

如果您想在创建子类时运行一些代码,无论它是在代码中还是在故事板/笔尖中创建的,那么您可以执行以下操作(基于Jeff Gu Kang’s answer)

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 文件初始化此视图 @VyachaslavGerchicov,我的回答表明您没有像接受的答案和问题一样使用 xibs 或故事板。问题指出“我不使用情节提要,所以我在代码中创建了所有 UI”。 下次你在 dealloc 方法中编写 fatalError 并告诉我们它不起作用,因为该类应该是单例。如果您更喜欢在代码中创建 UI 元素,那么您不应该手动禁止所有其他方式。最后的问题是如何创建“没有故事板的程序化”,但没有提到 xibs/nibs。在我的情况下,我需要以编程方式 + xib 创建一个单元格数组并将它们传递给DropDownMenuKit,这种方式不起作用,因为这个库的作者也禁止 xibs。 @VyachaslavGerchicov 听起来 Jeff Gu Kang’s answer 是您正在寻找的,因为它可以容纳 Storyboards/Xibs @VyachaslavGerchicov 该问题还指出“如果我从不实现框架和编码器,我该如何“隐藏”这个?”在创建永远不会在 Xib/Storyboard 中使用的 UIView 子类时,您可能会引入具有不同参数的初始化程序,这些参数无法由 init(coder:) 方法调用。如果您没有使用 fatalError 失败 init(coder:),如果不小心在 Xib/Storyboard 中使用它可能会导致非常混乱的问题。 fatalError 说明了这些意图。如已接受的答案所示,这是一种有意且常见的做法。【参考方案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
    

【讨论】:

【参考方案5】:

Swift 4.0,如果你想使用 xib 文件中的视图,那么这是给你的。我创建了 UIView 的 CustomCalloutView 类子类。我创建了一个 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)

【讨论】:

【参考方案6】:

这是我通常如何构建子类 (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)


【讨论】:

以上是关于Swift 子类 UIView的主要内容,如果未能解决你的问题,请参考以下文章

Swift 3:编译器无法识别子类 UIView 的类型

Swift 子类 UIView

如何从 Swift 中的 ViewController 获取 UIView 子类实例

Swift 中的 UIView 子类:IBOutlet 在展开时发现为零

Swift:错误:'必需'初始化程序'init(coder :)'必须由'UIView'的子类提供

UIView 子类在 Swift 中设置自己的高度 + UIViewController 中的约束 + 情节提要