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
Swift subclass UIView
提问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 UIView
and 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 initWithFrame
as 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 init
Methods
隐藏不需要的init
方法
My first suggestion is to declare a base UIView
to 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 BaseView
or 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 init
method. 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.height
and frame.width
within layoutSubviews()
to get the CustomView's dimensions for reference. This is demonstrated in the example below.
您可以参考frame.height
和frame.width
内layoutSubviews()
获取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 fatalError
in init(coder:)
为什么把fatalError
在init(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 setup
method 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)
}
}