Chcę uzyskać podklasę UIView
i pokazać login podobny do widoku. Stworzyłem to w Objective-C, ale teraz chcę przenieść to na Swift. Nie używam scenorysów, więc cały UI tworzę w kodzie.
Ale pierwszy problem polega na tym, że muszę wdrożyć initWithCoder
. Dałem mu domyślną implementację, ponieważ nie zostanie wywołana. Teraz, kiedy uruchamiam program, zawiesza się, bo też muszę zaimplementować initWithFrame
. Teraz mam to:
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")
}
Moje pytanie brzmi, gdzie powinienem utworzyć moje pole tekstowe itp. ... a jeśli nigdy nie zaimplementuję ramki i kodera, jak mogę to „ukryć”?
CGRectZero
Uważam, że zamiast używać , zaleca się użycieCGRect.zeroRect
.To jest prostsze.
override init (frame : CGRect) { super.init(frame : frame) // Do what you want. } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
źródło
Przykład niestandardowej podklasy UIView
Zwykle tworzę aplikacje na iOS bez używania scenorysów lub końcówek. Podzielę się kilkoma technikami, których nauczyłem się, aby odpowiedzieć na Twoje pytania.
Ukrywanie niechcianych
init
metodMoja pierwsza sugestia to zadeklarowanie bazy
UIView
do ukrycia niechcianych inicjatorów. Szczegółowo omówiłem to podejście w mojej odpowiedzi na temat „Jak ukryć inicjatory specyficzne dla serii ujęć i końcówek w podklasach interfejsu użytkownika” . Uwaga: to podejście zakłada, że nie będziesz używaćBaseView
ani jego elementów podrzędnych w scenorysach lub stalówkach, ponieważ celowo spowoduje to awarię aplikacji.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") } }
Twoja niestandardowa podklasa UIView powinna dziedziczyć z
BaseView
. Musi wywołać super.init () w swoim inicjatorze. Nie trzeba go wdrażaćinit(coder:)
. Pokazuje to poniższy przykład.Dodanie UITextField
Tworzę przechowywane właściwości dla widoków podrzędnych, do których istnieją odniesienia poza
init
metodą. Zazwyczaj robiłbym to dla UITextField. Wolę subviews instancji w deklaracji właściwości podrzędny tak:let textField = UITextField()
.UITextField nie będzie widoczny, chyba że dodasz go do listy widoku podrzędnego niestandardowego widoku przez wywołanie
addSubview(_:)
. Pokazuje to poniższy przykład.Układ programowy bez układu automatycznego
UITextField nie będzie widoczne, chyba że ustawisz jego rozmiar i położenie. Często tworzę układ w kodzie (nie używam Auto Layout) w ramach metody layoutSubviews .
layoutSubviews()
jest wywoływana początkowo i zawsze, gdy ma miejsce zdarzenie zmiany rozmiaru. Umożliwia to dostosowanie układu w zależności od rozmiaru CustomView. Na przykład, jeśli CustomView pojawia się w pełnej szerokości na różnych rozmiarach iPhone'ów i iPadów i dostosowuje się do obrotu, musi uwzględniać wiele początkowych rozmiarów i dynamicznie zmieniać rozmiar.Możesz odwołać się do
frame.height
iframe.width
w obrębie,layoutSubviews()
aby uzyskać wymiary CustomView w celach informacyjnych. Pokazuje to poniższy przykład.Przykładowa podklasa UIView
Niestandardowa podklasa UIView zawierająca UITextField, której nie trzeba implementować
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) } }
Układ programowy z układem automatycznym
Możesz również zaimplementować układ za pomocą automatycznego układu w kodzie. Ponieważ nie robię tego często, nie będę pokazywał przykładu. Przykłady implementacji automatycznego układu można znaleźć w kodzie na stronie Stack Overflow oraz w innych miejscach w Internecie.
Programmatic Layout Framework
Istnieją platformy open source, które implementują układ w kodzie. Interesuje mnie LayoutKit, ale nie próbowałem . Został napisany przez zespół programistów LinkedIn. Z repozytorium Github: „LinkedIn utworzył LayoutKit, ponieważ odkryliśmy, że Auto Layout nie jest wystarczająco wydajny dla skomplikowanych hierarchii widoków w przewijalnych widokach”.
Dlaczego umieścić
fatalError
winit(coder:)
Podczas tworzenia podklas UIView, które nigdy nie będą używane w scenorysie lub końcówce, możesz wprowadzić inicjatory z różnymi parametrami i wymaganiami inicjalizacyjnymi, których nie można wywołać za pomocą
init(coder:)
metody. Jeśli nie pomylisz init (kodera :) z afatalError
, może to prowadzić do bardzo mylących problemów, jeśli zostanie przypadkowo użyte w scenorysie / końcówce. FatalError potwierdza te zamiary.required init?(coder aDecoder: NSCoder) { fatalError("NSCoding not supported") }
Jeśli chcesz uruchomić jakiś kod, gdy podklasa jest tworzona, niezależnie od tego, czy jest tworzona w kodzie, czy w scenorysie / końcówce, możesz zrobić coś takiego (na podstawie odpowiedzi Jeffa Gu Kanga )
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 } }
źródło
fatalError
że zabronisz inicjowania tego widoku za pomocą plików xibfatalError
wewnątrz metody dealloc i powiesz nam, że to nie działa, ponieważ ta klasa powinna być singletonem. Jeśli wolisz tworzyć elementy interfejsu użytkownika w kodzie, nie powinieneś zabraniać ręcznie wszystkich innych sposobów. Na koniec pojawia się pytanie, jak tworzyć „programowo bez scenorysów”, ale nie wspomina się o xibs / nibs. W moim przypadku muszę stworzyć tablicę komórek z programowym + xib i przekazać je do niego,DropDownMenuKit
a ten sposób nie działa, ponieważ autor tej biblioteki zabrania również xibs.Ważne jest, aby Twój UIView mógł zostać utworzony za pomocą narzędzia do tworzenia interfejsu / scenorysów lub z kodu. Uważam, że warto mieć
setup
metodę ograniczania powielania kodu konfiguracji. na przykładclass 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 } }
źródło
Swift 4.0, jeśli chcesz korzystać z widoku z pliku xib, to jest to dla Ciebie. Utworzyłem klasę CustomCalloutView Podklasę UIView. Utworzyłem plik xib iw IB po prostu wybierz właściciela pliku, a następnie wybierz nazwę klasy inspektora atrybutów na CustomCalloutView, a następnie utwórz outlet w swojej klasie.
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) } }
// Teraz dodaję
let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50)) self.view.addSubview(viewCustom)
źródło
Oto przykład tego, jak zwykle tworzę podklasy (UIView). Mam zawartość jako zmienne, aby można było uzyskać do nich dostęp i zmodyfikować je później w innej klasie. Pokazałem również, jak używam automatycznego układu i dodawania treści.
Na przykład w ViewController mam ten widok zainicjowany w ViewDidLoad (), ponieważ jest on wywoływany tylko raz, gdy widok jest widoczny. Następnie używam tych funkcji, które tworzę tutaj
addContentToView()
i potem,activateConstraints()
do budowania treści i ustawiania ograniczeń. Jeśli później w ViewController chcę, aby kolor, powiedzmy, przycisku był czerwony, po prostu robię to w tej konkretnej funkcji w tym ViewController. Coś jak: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) } }
źródło