ios - descargar - swift download
Subclase Swift UIView (6)
Ejemplo de subclase de UIView personalizada
Por lo general, creo aplicaciones de iOS sin usar guiones gráficos o plumillas. Compartiré algunas técnicas que he aprendido para responder sus preguntas.
Ocultar métodos de
init
no deseados
Mi primera sugerencia es declarar una base
UIView
para ocultar los inicializadores no deseados.
He discutido este enfoque en detalle en
mi respuesta a "Cómo ocultar Storyboard y Nib Inicializadores específicos en subclases de UI"
.
Nota: Este enfoque supone que no usará
BaseView
o sus descendientes en guiones gráficos o plumillas, ya que intencionalmente hará que la aplicación se bloquee.
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")
}
}
Su subclase de UIView personalizada debe heredar de
BaseView
.
Debe llamar a super.init () en su inicializador.
No necesita implementar
init(coder:)
.
Esto se demuestra en el siguiente ejemplo.
Agregar un UITextField
Creo propiedades almacenadas para subvistas referenciadas fuera del método
init
.
Normalmente lo haría para un UITextField.
Prefiero crear instancias de subvistas dentro de la declaración de la propiedad de subvista de esta manera:
let textField = UITextField()
.
UITextField no estará visible a menos que lo agregue a la lista de subvista de la vista personalizada llamando a
addSubview(_:)
.
Esto se demuestra en el siguiente ejemplo.
Diseño programático sin diseño automático
UITextField no será visible a menos que establezca su tamaño y posición.
A menudo hago el diseño en código (sin usar Diseño automático) dentro del
método layoutSubviews
.
layoutSubviews()
se llama inicialmente y cada vez que ocurre un evento de cambio de tamaño.
Esto permite ajustar el diseño según el tamaño de CustomView.
Por ejemplo, si CustomView aparece en todo el ancho en varios tamaños de iPhones y iPads y se ajusta para la rotación, debe acomodar muchos tamaños iniciales y cambiar su tamaño dinámicamente.
Puede consultar
frame.height
y
frame.width
dentro de
layoutSubviews()
para obtener las dimensiones de CustomView como referencia.
Esto se demuestra en el siguiente ejemplo.
Ejemplo de subclase UIView
Una subclase de UIView personalizada que contiene un UITextField que no necesita implementar
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)
}
}
Diseño programático con diseño automático
También puede implementar el diseño utilizando el diseño automático en el código. Como no suelo hacer esto, no mostraré un ejemplo. Puede encontrar ejemplos de implementación del diseño automático en el código de Stack Overflow y en otros lugares de Internet.
Marcos de diseño programático
Existen marcos de código abierto que implementan el diseño en código. Uno en el que estoy interesado pero que no he probado es LayoutKit . Fue escrito por el equipo de desarrollo y LinkedIn. Desde el repositorio de Github: "LinkedIn creó LayoutKit porque descubrimos que el diseño automático no tiene el rendimiento suficiente para jerarquías de vista complicadas en vistas desplazables".
¿Por qué poner
fatalError
en
init(coder:)
Al crear subclases de UIView que nunca se usarán en un guión gráfico o plumín, puede introducir inicializadores con diferentes parámetros y requisitos de inicialización que el método
init(coder:)
no podría
init(coder:)
.
Si no falló init (codificador :) con un
fatalError
, podría provocar problemas muy confusos en el
fatalError
si se usa accidentalmente en un guión gráfico / plumín.
El fatalError afirma estas intenciones.
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
Si desea ejecutar algún código cuando se crea la subclase, independientemente de si se creó en código o en un guión gráfico / plumín, entonces podría hacer algo como lo siguiente (según la respuesta de 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
}
}
Acabo de empezar a usar Swift. Leí el libro y aprendo mejor mientras lo hacía, comencé algo simple.
Quiero subclasificar
UIView
y mostrar un inicio de sesión como vista.
He creado esto en Objective-C pero ahora quiero portarlo a Swift.
No uso guiones gráficos, por lo que creo toda mi interfaz de usuario en código.
Pero el primer problema es que debo implementar
initWithCoder
.
Le di una implementación predeterminada ya que no se llamará.
Ahora, cuando ejecuto el programa, se bloquea porque tengo que implementar
initWithFrame
también.
Ahora tengo esto:
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")
}
Mi pregunta es ¿dónde debería crear mi campo de texto, etc ... y si nunca implemento el marco y el codificador, ¿cómo puedo "ocultar" esto?
Aquí hay un ejemplo de cómo suelo construir mis subclases (UIView). Tengo el contenido como variables para que pueda accederse y modificarse tal vez más tarde en alguna otra clase. También he mostrado cómo uso el diseño automático y agrego contenido.
Por ejemplo, en un ViewController tengo esta vista inicializada en ViewDidLoad () ya que solo se llama una vez cuando la vista es visible.
Luego uso estas funciones que hago aquí
addContentToView()
y luego
addContentToView()
activateConstraints()
para construir el contenido y establecer restricciones.
Si más adelante en un ViewController quiero que el color de un botón sea rojo, solo hago eso en esa función específica en ese ViewController.
Algo así como:
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)
}
}
Es importante que su UIView se pueda crear mediante el generador de interfaces / guiones gráficos o desde el código.
Creo que es útil tener un método de
setup
para reducir la duplicación de cualquier código de configuración.
p.ej
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
}
}
Esto es mas simple.
override init (frame : CGRect) {
super.init(frame : frame)
// Do what you want.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Swift 4.0, si desea usar la vista desde el archivo xib, entonces esto es para usted. Creé la clase CustomCalloutView Subclase de UIView. He creado un archivo xib y en IB simplemente seleccione el propietario del archivo y luego seleccione el nombre de la clase del inspector de atributos para CustomCalloutView, luego cree una salida en su clase.
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)
}
}
// Ahora agregándolo
let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
self.view.addSubview(viewCustom)
Usualmente hago algo como esto, es un poco detallado.
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()
(Editar: he editado mi respuesta para que la relación entre los inicializadores sea más clara)