ios Swift 子类 UIView

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27056864/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 03:44:57  来源:igfitidea点击:

Swift subclass UIView

iosswift

提问by Haagenti

I've just started using Swift. I've read the book and I learn better while doing so I started something simple.

我刚刚开始使用 Swift。我读过这本书并且在这样做的同时我学得更好,所以我开始了一些简单的事情。

I want to subclass UIViewand show a login like view. I've created this in Objective-C but now I want to port it to Swift. I do not use storyboards, so I create all my UI in code.

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

But the first problem is that I must implement 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 initWithFrameas well. Now I got this:

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

My question is where should I create my textfield etc... and if I never implement frame and coder how can I "hide" this?

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

回答by Raymond

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)

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

回答by Jeff Gu Kang

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

回答by Mobile Dan

Custom UIView Subclass Example

自定义 UIView 子类示例

I usually create iOS apps without using storyboards or nibs. I'll share some techniques I've learned to answer your questions.

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

 

 

Hiding Unwanted initMethods

隐藏不需要的init方法

My first suggestion is to declare a base UIViewto hide unwanted initializers. I've discussed this approach in detail in my answer to "How to Hide Storyboard and Nib Specific Initializers in UI Subclasses". Note: This approach assumes you will not use BaseViewor its descendants in storyboards or nibs since it will intentionally cause the app to crash.

我的第一个建议是声明一个基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")
    }
}

Your custom UIView subclass should inherit from BaseView. It must call super.init() in its initializer. It does not need to implement init(coder:). This is demonstrated in the example below.

您的自定义 UIView 子类应该从BaseView. 它必须在其初始值设定项中调用 super.init()。它不需要实现init(coder:)。这在下面的示例中进行了演示。

 

 

Adding a UITextField

添加一个 UITextField

I create stored properties for subviews referenced outside of the initmethod. I would typically do so for a UITextField. I prefer to instantiate subviews within the declaration of the subview property like this: let textField = UITextField().

我为在init方法之外引用的子视图创建存储属性。我通常会为 UITextField 这样做。我更喜欢以实例子视图像这样子视图属性的声明中:let textField = UITextField()

The UITextField will not be visible unless you add it to the custom view's subview list by calling addSubview(_:). This is demonstrated in the example below.

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

 

 

Programmatic Layout Without Auto Layout

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

The UITextField will not be visible unless you set its size and position. I often do layout in code (not using Auto Layout) within the layoutSubviews method. layoutSubviews()is called initially and whenever a resize event happens. This allows adjusting layout depending on the size of 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.

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

You can refer to frame.heightand frame.widthwithin layoutSubviews()to get the CustomView's dimensions for reference. This is demonstrated in the example below.

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

 

 

Example UIView Subclass

示例 UIView 子类

A custom UIView subclass containing a UITextField which does not need to implement init?(coder:).

包含不需要实现的 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)
    }
}

 

 

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. It was written by the development team an 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."

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

 

 

Why put fatalErrorin init(coder:)

为什么把fatalErrorinit(coder:)

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. 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. The fatalError asserts these intentions.

在创建永远不会在故事板或笔尖中使用的 UIView 子类时,您可能会引入具有不同参数和初始化要求的初始化程序,这些参数和初始化要求无法被init(coder:)方法调用。如果您没有使用 a 使 init(coder:) 失败fatalError,如果不小心在故事板/笔尖中使用,可能会导致非常混乱的问题。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
    }
}

回答by Jason Moore

It's important that your UIView can be created by interface builder/storyboards or from code. I find it's useful to have a setupmethod to reduce duplicating any setup code. e.g.

重要的是,您的 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
    }
}

回答by Gurjinder Singh

Swift 4.0,If you want to use view from xib file, then this is for you. I created CustomCalloutView class Sub class of 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.

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

回答by Starlord

Here's an example of how I usually build my subclasses(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.

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

For example in a ViewController I have this view initialized In ViewDidLoad() since that is only called once when the view is visible. Then I use these functions I make here addContentToView()and then activateConstraints()to build the content and set constraints. 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. Something like: func tweaksome(){ self.customView.someButton.color = UIColor.red}

例如在 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)
}
}