简体   繁体   English

Swift 子类 UIView

[英]Swift subclass UIView

I want to subclass UIView and show a login like view.我想UIView并显示类似视图的登录。 I've created this in Objective-C, but now I want to port it to Swift.我已经在 Objective-C 中创建了它,但现在我想将它移植到 Swift。 I do not use storyboards, so I create all my UI in code.我不使用故事板,所以我在代码中创建了所有的 UI。

But the first problem is that I must implement initWithCoder .但第一个问题是我必须实现initWithCoder I gave it a default implementation since It won't be called.我给了它一个默认实现,因为它不会被调用。 Now when I run the program it crashes, because I've to implement initWithFrame as well.现在当我运行程序时它崩溃了,因为我还必须实现initWithFrame Now I got this:现在我得到了这个:

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

My question is where should I create my textfield etc... and if I never implement frame and coder how can I "hide" this?我的问题是我应该在哪里创建我的文本字段等......如果我从不实现框架和编码器,我该如何“隐藏”它?

I usually do something like this, its a bit verbose.我通常做这样的事情,它有点冗长。

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

(Edit: I've edited my answer so that the relation between the initializers is more clear) (编辑:我已经编辑了我的答案,以便初始值设定项之间的关系更加清晰)

This is more simple.这更简单。

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

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

Custom UIView Subclass Example自定义 UIView 子类示例

I usually create iOS apps without using storyboards or nibs.我通常在不使用故事板或笔尖的情况下创建 iOS 应用程序。 I'll share some techniques I've learned to answer your questions.我将分享一些我学到的技巧来回答你的问题。

Hiding Unwanted init Methods隐藏不需要的init方法

My first suggestion is to declare a base UIView to hide unwanted initializers.我的第一个建议是声明一个基本的UIView来隐藏不需要的初始值设定项。 I've discussed this approach in detail in my answer to "How to Hide Storyboard and Nib Specific Initializers in UI Subclasses" .我在“如何在 UI 子类中隐藏 Storyboard 和 Nib 特定初始化程序”的回答中详细讨论了这种方法。 Note: This approach assumes you will not use BaseView or its descendants in storyboards or nibs since it will intentionally cause the app to crash.注意:此方法假设您不会在故事板或笔尖中使用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")
    }
}

Your custom UIView subclass should inherit from BaseView .您的自定义 UIView 子类应该从BaseView继承。 It must call super.init() in its initializer.它必须在其初始值设定项中调用 super.init()。 It does not need to implement init(coder:) .它不需要实现init(coder:) This is demonstrated in the example below.这在下面的示例中进行了演示。

Adding a UITextField添加 UITextField

I create stored properties for subviews referenced outside of the init method.我为在init方法之外引用的子视图创建存储属性。 I would typically do so for a UITextField.我通常会为 UITextField 这样做。 I prefer to instantiate subviews within the declaration of the subview property like this: let textField = UITextField() .我更喜欢在 subview 属性的声明中实例化子视图,如下所示: let textField = UITextField()

The UITextField will not be visible unless you add it to the custom view's subview list by calling addSubview(_:) . UITextField 将不可见,除非您通过调用addSubview(_:)将其添加到自定义视图的子视图列表中。 This is demonstrated in the example below.这在下面的示例中进行了演示。

Programmatic Layout Without Auto Layout没有自动布局的程序化布局

The UITextField will not be visible unless you set its size and position.除非您设置其大小和位置,否则 UITextField 将不可见。 I often do layout in code (not using Auto Layout) within the layoutSubviews method .我经常在layoutSubviews 方法中的代码中进行布局(不使用自动布局)。 layoutSubviews() is called initially and whenever a resize event happens. layoutSubviews()最初和每当发生调整大小事件时都会被调用。 This allows adjusting layout depending on the size of CustomView.这允许根据 CustomView 的大小调整布局。 For example, if CustomView appears the full width on various sizes of iPhones and iPads and adjusts for rotation, it needs to accommodate many initial sizes and resize dynamically.例如,如果 CustomView 在各种尺寸的 iPhone 和 iPad 上显示全宽并进行旋转调整,则它需要容纳许多初始尺寸并动态调整大小。

You can refer to frame.height and frame.width within layoutSubviews() to get the CustomView's dimensions for reference.您可以参考layoutSubviews() frame.heightframe.width来获取 CustomView 的尺寸以供参考。 This is demonstrated in the example below.这在下面的示例中进行了演示。

Example UIView Subclass示例 UIView 子类

A custom UIView subclass containing a UITextField which does not need to implement init?(coder:) .包含不需要实现init?(coder:)的 UITextField 的自定义 UIView 子类。

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

Programmatic Layout with Auto Layout具有自动布局的程序化布局

You can also implement layout using Auto Layout in code.您还可以在代码中使用自动布局来实现布局。 Since I don't often do this, I will not show an example.由于我不经常这样做,我不会举例说明。 You can find examples of implementing Auto Layout in code on Stack Overflow and elsewhere on the Internet.您可以在 Stack Overflow 和 Internet 上的其他地方找到在代码中实现自动布局的示例。

Programmatic Layout Frameworks程序化布局框架

There are open source frameworks that implement layout in code.有一些开源框架可以在代码中实现布局。 One I am interested in but have not tried is LayoutKit .我感兴趣但没有尝试过的是LayoutKit It was written by the development team an LinkedIn.它是由开发团队和 LinkedIn 编写的。 From the Github repository: "LinkedIn created LayoutKit because we have found that Auto Layout is not performant enough for complicated view hierarchies in scrollable views."来自 Github 存储库:“LinkedIn 创建了 LayoutKit,因为我们发现自动布局对于可滚动视图中的复杂视图层次结构来说性能不够。”

Why put fatalError in init(coder:)为什么要在init(coder:) fatalError

When creating UIView subclasses that will never be used in a storyboard or nib, you might introduce initializers with different parameters and initialization requirements that could not be called by the init(coder:) method.在创建永远不会在故事板或笔尖中使用的 UIView 子类时,您可能会引入具有不同参数和初始化要求的初始化程序,这些初始化程序不能被init(coder:)方法调用。 If you did not fail init(coder:) with a fatalError , it could lead to very confusing problems down the line if accidentally used in a storyboard/nib.如果您没有因 init(coder:) 失败而出现fatalError ,如果不小心在故事板/笔尖中使用,可能会导致非常混乱的问题。 The fatalError asserts these intentions. FatalError 断言了这些意图。

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

If you want to run some code when the subclass is created regardless of whether it is created in code or a storyboard/nib then you could do something like the following (based on Jeff Gu Kang's answer )如果您想在创建子类时运行一些代码,而不管它是在代码中创建还是在故事板/笔尖中创建,那么您可以执行以下操作(基于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
    }
}

It's important that your UIView can be created by interface builder/storyboards or from code.重要的是,您的 UIView 可以由界面构建器/故事板或代码创建。 I find it's useful to have a setup method to reduce duplicating any setup code.我发现有一个setup方法来减少任何设置代码的重复很有用。 eg例如

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

Swift 4.0,If you want to use view from xib file, then this is for you. Swift 4.0,如果您想使用来自 xib 文件的视图,那么这适合您。 I created CustomCalloutView class Sub class of UIView.我创建了 CustomCalloutView 类 UIView 的子类。 I have created a xib file and in IB just select file owner then select Attribute inspector set class name to CustomCalloutView, then create outlet in your class.我创建了一个 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)
        }
    }

// Now adding it // 现在添加它

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

Here's an example of how I usually build my subclasses(UIView).这是我通常如何构建子类(UIView)的示例。 I have the content as variables so they can be accessed and tweaked maybe later in some other class.我将内容作为变量,以便稍后在其他类中可以访问和调整它们。 I've also shown how I use auto layout and adding content.我还展示了如何使用自动布局和添加内容。

For example in a ViewController I have this view initialized In ViewDidLoad() since that is only called once when the view is visible.例如在 ViewController 中,我在 ViewDidLoad() 中初始化了这个视图,因为它只在视图可见时调用一次。 Then I use these functions I make here addContentToView() and then activateConstraints() to build the content and set constraints.然后我使用我在这里创建的这些函数addContentToView()然后activateConstraints()来构建内容并设置约束。 If I later in a ViewController want the color of let's say a button to be red, I just do that in that specific function in that ViewController.如果我稍后在 ViewController 中希望按钮的颜色为红色,我只需在该 ViewController 的特定功能中执行此操作。 Something like: func tweaksome(){ self.customView.someButton.color = UIColor.red}类似于: 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)
}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM