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
그러나 addBehavior는 initFrame이 호출되고 초기화되기 때문에 두 번 호출됩니다. 내 코드를 실행하면 첫 번째 프레임 init가 인쇄되고 기본 init
Haagenti

6
좋은 물건, 감사합니다. 사용하는 대신 사용 CGRectZero하는 것이 좋습니다 CGRect.zeroRect.
Mr Rogers

57
이 이니셜 라이저는 매우 복잡합니다.
Ian Warburton 2015

3
자동 레이아웃에 대해이 작업을 수행 할 수있는 방법이 있습니까? 프레임이 너무 구식입니다.
devios1

8
이것은 완전한 대답이 아닙니다. UIView는 initWithCoding을 지원합니다. 펜촉 또는 스토리 보드에서로드 된 모든 뷰는 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 하위 클래스에서 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")
    }
}

사용자 지정 UIView 하위 클래스는 BaseView. 이니셜 라이저에서 super.init ()를 호출해야합니다. 구현할 필요가 없습니다 init(coder:). 이것은 아래 예에서 설명됩니다.

 

UITextField 추가

init메서드 외부에서 참조되는 하위 뷰에 대해 저장된 속성을 만듭니다 . 나는 일반적으로 UITextField에 대해 그렇게 할 것입니다. 다음과 같이 subview 속성의 선언 내에서 하위 뷰를 인스턴스화하는 것을 선호합니다 let textField = UITextField().

을 호출하여 사용자 정의보기의 하위보기 목록에 추가하지 않는 한 UITextField가 표시되지 않습니다 addSubview(_:). 이것은 아래 예에서 설명됩니다.

 

자동 레이아웃이없는 프로그래밍 레이아웃

UITextField는 크기와 위치를 설정하지 않는 한 표시되지 않습니다. 나는 종종 layoutSubviews 메서드 내에서 코드 (자동 레이아웃을 사용하지 않음)로 레이아웃을 수행합니다 . layoutSubviews()처음에 그리고 크기 조정 이벤트가 발생할 때마다 호출됩니다. 이를 통해 CustomView의 크기에 따라 레이아웃을 조정할 수 있습니다. 예를 들어 CustomView가 다양한 크기의 iPhone 및 iPad에서 전체 너비로 표시되고 회전에 맞게 조정되는 경우 많은 초기 크기를 수용하고 동적으로 크기를 조정해야합니다.

참조를 위해 CustomView의 치수를 가져 오기 위해 frame.heightframe.width내부 layoutSubviews()를 참조 할 수 있습니다 . 이것은 아래 예에서 설명됩니다.

 

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

스토리 보드 또는 펜촉에서 사용되지 않는 UIView 하위 클래스를 만들 때 init(coder:)메서드에서 호출 할 수없는 다른 매개 변수와 초기화 요구 사항이있는 이니셜 라이저를 도입 할 수 있습니다 . init (coder :)를으로 실패하지 않은 fatalError경우 실수로 스토리 보드 / 니브에서 사용하는 경우 매우 혼란스러운 문제가 발생할 수 있습니다. 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
    }
}

과? 추가하여 fatalError당신은 XIB 파일이보기 초기화하기 위해서 금지
Vyachaslav Gerchicov

@VyachaslavGerchicov, 내 대답은 수락 된 대답과 질문처럼 xib 또는 스토리 보드를 사용하지 않는다는 가정을 나타냅니다. 질문은 "스토리 보드를 사용하지 않기 때문에 모든 UI를 코드로 작성합니다"라고합니다.
Mobile Dan

다음에 fatalErrordealloc 메소드 내부 를 작성 하고 해당 클래스가 싱글 톤이어야하므로 작동하지 않는다고 알려주십시오. 코드에서 UI 요소를 만드는 것을 선호한다면 다른 모든 방법을 수동으로 금지해서는 안됩니다. 마지막으로 질문은 "스토리 보드없이 프로그래밍 방식으로"생성하는 방법이지만 xibs / nib에 대해서는 언급하지 않습니다. 제 경우에는 프로그래밍 방식으로 + xib를 사용하여 셀 배열을 만들고 전달해야 DropDownMenuKit하며이 라이브러리의 작성자도 xib를 금지하기 때문에이 방법이 작동하지 않습니다.
Vyachaslav Gerchicov

같은 @VyachaslavGerchicov 그것은 소리 제프 구 강의 대답은 당신이 스토리 보드 / Xibs 수용 이후 찾고있는 무슨이다
모바일 댄

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 파일에서보기를 사용하려면 이것은 당신을위한 것입니다. UIView의 CustomCalloutView 클래스 Sub 클래스를 만들었습니다. xib 파일을 만들었고 IB에서 파일 소유자를 선택한 다음 Attribute inspector 클래스 이름을 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.