SwiftサブクラスUIView


95

UIViewビューのようなログインをサブクラス化して表示したい。これはObjective-Cで作成しましたが、Swiftに移植したいと思います。私はストーリーボードを使用しないので、すべてのUIをコードで作成します。

しかし、最初の問題は、実装しなければならないこと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")
}

私の質問は、テキストフィールドなどをどこに作成するべきかということです。フレームとコーダーを実装しない場合、これをどのように「隠す」ことができますか?

回答:


174

私は通常このようなことをします、それは少し冗長です。

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

(編集:私はイニシャライザ間の関係がより明確になるように私の回答を編集しました)


4
ただし、initFrameが呼び出され、initが呼び出されるため、addBehaviorは2回呼び出されます。私のコードを実行すると、最初のフレームinitが出力され、デフォルトのinit
Haagenti

6
良いもの、ありがとう。を使用する代わりに、を使用するCGRectZeroことをお勧めしますCGRect.zeroRect
ロジャース氏

57
このイニシャライザのものは非常に複雑です。
Ian Warburton、

3
自動レイアウトでこれを行う方法はありますか?フレームが古くなっています。
devios1 2016

8
これは完全な答えではありません。UIViewはinitWithCodingをサポートします。nibまたはストーリーボードからロードされたビューはinitWithCodingメソッドを呼び出し、クラッシュします。
Iain Delaney、

17

これはもっと簡単です。

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

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

10

カスタムUIViewサブクラスの例

私は通常、ストーリーボードやペン先を使わずにiOSアプリを作成します。あなたの質問に答えるために学んだいくつかのテクニックを紹介します。

 

不要なinitメソッドを非表示にする

最初の提案は、UIView不要な初期化子を隠すベースを宣言することです。この方法については、「UIサブクラスでストーリーボードとニブ固有の初期化子を非表示にする方法」への回答で詳しく説明しました。注:このアプローチは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")
    }
}

カスタムUIViewサブクラスはから継承する必要がありBaseViewます。初期化子でsuper.init()を呼び出す必要があります。実装する必要はありませんinit(coder:)。これは、以下の例で示されています。

 

UITextFieldの追加

外部で参照されるサブビューの保存されたプロパティを作成します initメソッドの。私は通常、UITextFieldに対してそうします。次のように、subviewプロパティの宣言内でサブビューをインスタンス化することを好みますlet textField = UITextField()

呼び出してカスタムビューのサブビューリストに追加しない限り、UITextFieldは表示されません。 addSubview(_:)。これは、以下の例で示されています。

 

自動レイアウトなしのプログラムによるレイアウト

サイズと位置を設定しない限り、UITextFieldは表示されません。私は多くの場合、layoutSubviewsメソッド内で(自動レイアウトを使用せずに)コードでレイアウトを行いますlayoutSubviews()最初に呼び出され、サイズ変更イベントが発生するたびに呼び出されます。これにより、CustomViewのサイズに応じてレイアウトを調整できます。たとえば、CustomViewがさまざまなサイズのiPhoneおよびiPadで全幅に表示され、回転に合わせて調整される場合、多くの初期サイズに対応し、動的にサイズ変更する必要があります。

あなたはを参照することができますframe.heightし、frame.width中にlayoutSubviews()参照のためのCustomViewの寸法を取得します。これは、以下の例で示されています。

 

UIViewサブクラスの例

実装する必要のない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)
    }
}

 

自動レイアウトを使用したプログラムによるレイアウト

コードで自動レイアウトを使用してレイアウトを実装することもできます。私はこれを頻繁に行わないので、例を示しません。自動レイアウトの実装例は、Stack Overflowやインターネット上の他の場所のコードにあります。

 

プログラムによるレイアウトフレームワーク

コードでレイアウトを実装するオープンソースフレームワークがあります。私が興味を持っているが試していないのはLayoutKitです。それはLinkedInの開発チームによって書かれました。Githubリポジトリから:「LinkedInはLayoutKitを作成しました。スクロール可能なビューの複雑なビュー階層に対して自動レイアウトが十分に機能しないことが判明したためです。」

 

なぜ入れfatalErrorinit(coder:)

ストーリーボードまたはnibで決して使用されないUIViewサブクラスを作成するとき、init(coder:)メソッドによって呼び出すことができなかった異なるパラメーターと初期化要件を持つ初期化子を導入する場合があります。init(coder :)をで失敗させなかったfatalError場合、誤ってストーリーボード/ nibで使用すると、混乱を招く問題が発生する可能性があります。fatalErrorはこれらの意図を表明します。

required init?(coder aDecoder: NSCoder) {
    fatalError("NSCoding not supported")
}

サブクラスがコードで作成されたか、ストーリーボード/ニブで作成されたかに関係なく、サブクラスの作成時にコードを実行する場合は、次のようなことを実行できます(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
    }
}

そして?fatalErrorxibファイルでこのビューを初期化することを禁止することで追加
Vyachaslav Gerchicov

@VyachaslavGerchicov、私の回答は、承認された回答や質問と同様に、xibsやストーリーボードを使用していないという仮定を述べています。質問には、「ストーリーボードは使用しないので、すべてのUIをコードで作成します」と記載されています。
Mobile Dan

次にfatalErrordeallocメソッドの内部に書き込み、そのクラスがシングルトンであるべきだから機能しないと私たちに言ったとき。コードでUI要素を作成する場合は、他のすべての方法を手動で禁止しないでください。最後に問題は、「ストーリーボードなしでプログラムで」作成する方法ですが、xibs / nibsは言及されていません。私の場合、プログラムで+ xibを使用してセルの配列を作成し、それらを渡す必要がDropDownMenuKitあります。このライブラリの作成者もxibsを禁止しているため、この方法は機能しません。
Vyachaslav Gerchicov

@VyachaslavGerchicov ストーリーボード/ Xibsに対応しているため、Jeff Gu Kangの答えがあなたの探しているもののようです
Mobile Dan

1
@VyachaslavGerchicov質問には、「フレームとコーダーを実装しない場合、どうすればこれを「隠す」ことができるのか」という質問もありました。Xib / Storyboardで使用されることのないUIViewサブクラスを作成する場合、init(coder :)メソッドでは呼び出せないさまざまなパラメーターを持つ初期化子を導入する場合があります。fatalErrorでinit(coder :)を失敗させなかった場合、Xib / Storyboardで誤って使用すると、混乱を招く問題が発生する可能性があります。fatalErrorはこれらの意図を示しています。これは、受け入れられた回答に見られるように、意図的で一般的な方法です。
Mobile Dan

4

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

4

Swift 4.0、xibファイルからのビューを使用したい場合、これはあなたのためです。UICallのCustomCalloutViewクラスのサブクラスを作成しました。私は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)
        }
    }

//追加します

    let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
    self.view.addSubview(viewCustom)

-1

これが、通常、サブクラス(UIView)を構築する方法の例です。私はコンテンツを変数として持っているので、それらはおそらく他のクラスでアクセスして調整することができます。自動レイアウトを使用してコンテンツを追加する方法も示しました。

たとえば、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)
}
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.