Swift podklasa UIView

95

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ć”?

Haagenti
źródło

Odpowiedzi:

174

Zwykle robię coś takiego, jest to trochę rozwlekłe.

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

(Edytuj: zredagowałem moją odpowiedź, aby relacja między inicjatorami była bardziej przejrzysta)

Raymond
źródło
4
Ale addBehavior jest wywoływana dwukrotnie, ponieważ initFrame jest wywoływana i init. Jeśli uruchomisz mój kod, drukowana jest pierwsza ramka init, a następnie domyślny init
Haagenti
6
Dobra rzecz, dzięki. CGRectZeroUważam, że zamiast używać , zaleca się użycie CGRect.zeroRect.
Pan Rogers
57
Ta inicjalizacja jest szalenie skomplikowana.
Ian Warburton
3
Czy istnieje sposób, aby to zrobić w przypadku automatycznego układu? Rama jest tak przestarzała.
devios1
8
To nie jest pełna odpowiedź. UIView obsługuje initWithCoding. Każdy widok załadowany z końcówki lub serii ujęć wywoła metodę initWithCoding i ulegnie awarii.
Iain Delaney
17

To jest prostsze.

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

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}
Jeff Gu Kang
źródło
10

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 initmetod

Moja pierwsza sugestia to zadeklarowanie bazy UIViewdo 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ć BaseViewani 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 initmetodą. 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.heighti frame.widthw 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ć fatalErrorwinit(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 a fatalError, 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
    }
}
Mobile Dan
źródło
i? dodając, fatalErrorże zabronisz inicjowania tego widoku za pomocą plików xib
Vyachaslav Gerchicov
@VyachaslavGerchicov, Moja odpowiedź zawiera założenie, że nie używasz XIBS ani scenorysów, tak jak przyjęta odpowiedź i pytanie. Pytanie zawiera informację „Nie używam scenorysów, więc cały interfejs użytkownika tworzę w kodzie”.
Mobile Dan
następnym razem napiszesz fatalErrorwewną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, DropDownMenuKita ten sposób nie działa, ponieważ autor tej biblioteki zabrania również xibs.
Vyachaslav Gerchicov
@VyachaslavGerchicov Wygląda na to, że odpowiedź Jeffa Gu Kanga jest tym, czego szukasz, ponieważ zawiera Storyboards / Xibs
Mobile Dan
1
@VyachaslavGerchicov Pytanie również brzmiało: „a jeśli nigdy nie zaimplementuję ramki i kodera, jak mogę to„ ukryć ”? Podczas tworzenia podklas UIView, które nigdy nie będą używane w Xib / Storyboard, możesz wprowadzić inicjatory z różnymi parametrami, których nie można wywołać metodą init (koder :). Jeśli nie zawiodłeś init (kodera :) z fatalError, może to prowadzić do bardzo zagmatwanych problemów, jeśli przypadkowo zostanie użyte w Xib / Storyboard. FatalError określa te zamiary. Jest to celowa i powszechna praktyka, jak widać w przyjętej odpowiedzi.
Mobile Dan
4

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ć setupmetodę ograniczania powielania kodu konfiguracji. na przykład

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
    }
}
Jason Moore
źródło
4

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)
Gurjinder Singh
źródło
-1

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)
}
}
Gwiezdny władca
źródło