IOS UIkit- 1

김정현·2023년 4월 17일
0

UIKit

목록 보기
6/14

페이지 이동 버튼

우선 StroyBoard 형식으로 실행한다. 그리고

viewController: 이곳에서 코드를 작성한다.

Main: 버튼, 라벨 등등 인터페이스상으로 설치하고 눈으로 확인할 수 있다.

  1. 위에 창에 우클릭 - newfile을 통해 새로운 탭을 형성할 수 있다.
    새로운 viewController창을 생성한다.
  • 우클릭 - newfile - cocoa touch class - 이름설정(xib체크)

(xib를 체크할시 해당하는 컨트롤러에 view도 하나 생성된다.)

  1. Main창에서 위 사진의 1 버튼을 눌러 새로운 버튼을 만든다. 그리고 2를 눌러 화면을 분할시킨다.

버튼을 오른쪽 버튼으로 드래그하여 Main창에 입력 시킨다. 그리고

@IBAction func movetoDetail(_ sender: Any) {
        let detailVc = DetailViewController(nibName: "DetailViewController", bundle: nil)  //옮겨갈 창에 이름을 적음. String 형식으로 (Main에 있는 것이 아니기 때문에 코드로 사용하기 위해 인스턴스화 시킴)
        
        self.present(detailVc, animated: true, completion: nil)

코드를 작성하면 버튼을 눌렀을 때 다른창으로 이동하는 함수가 완성된다.

데이터를 넘겨주는 6가지 방법

instance property

데이터를 넘겨주는 쪽에서 데이터를 받는 쪽을 인스턴스화 시켜 받는 쪽 프로퍼티에 값을 할당해 데이터를 넘겨주는 방식이다.

viewCotroller가 라벨 데이터를 넘겨주고 DetailView Controller의 라벨 데이터를 변화시킨다.

  • DetailView Controller
import UIKit

class DetailViewController: UIViewController {
    
    var someString = ""    //빈 프로퍼티를 만들어준다.
    
    @IBOutlet var someLabel: UILabel!   // 라벨을 만든 후 드래그해서 아울렛을 만들어줌
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        someLabel.text = someString    //위에서 만든 라벨에 어떤 텍스트가 들어갈지 정한다. 
    }
    
    
}
  • viewCotroller
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
       
    }

    @IBAction func movetoDetail(_ sender: Any) {
        let detailVc = DetailViewController(nibName: "DetailViewController", bundle: nil)  //데이터 받는쪽을 인스턴스화 시킴
       
        detailVC.someString = "aaa스트링"    //DetailViewController에서 만든 프로퍼티에 값에 데이터를 줌
       
        self.present(detailVc, animated: true, completion: nil)  //DetailViewController 창이 화면에 나타나게하는 코드

    }
    
}

이런식으로 구성되는데, 주의할 점은 만약 someString처럼 미리 만들어둔 프로퍼티에 접근하는 것이아니라 직접 아울렛 값에 영향을 주려한다면 에러가 발생한다. 그 이유는 아울렛은 present 즉, 이 화면을 출력하기 전에는 nil을 가지고 있기 때문에 그전에는 접근할 수가 없다.
그러므로 직접 접근하려면 present 후에 접근하거나, 아니면 위 코드처럼 미리 프로퍼티를 만들고 그 프로퍼티에 접근하는 것이 옳다. 후자가 앱개발에서 지향하는 방향이다.

Segue

Segue를 사용한 방법은 present를 아래 사진처럼 view창에서 직접 연결하여 따로 기입하지 않고 identifier를 생성하여 데이터를 이동시키는 방법이다.

오른쪽으로 드래그하여 생성할 수 있으며 몇개고 생성이 가능하다. 화살표를 클릭하면 Identifier 이름 변경이 가능하다. 이름을 설정한 뒤 , prepare라는 함수를 사용하여 데이터를 이동시킨다.
그리고, 오른쪽 화면과 연결시킬 새로운 클래스 컨트롤러를 생성한다.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "segueDetail" {     //identifier 이름은  segueDetail
           if let detailVC = segue.destination as? segueDetailviewController {  //타입캐스팅을 해서 segueDetailviewController에서 프로퍼티를 가져옴
                detailVC.dataStirng = "abcd"
           }
        }
    }

이 방법도 인스턴스 프로퍼티와 마찬가지로 직접 접근하는 것은 좋지 않다. 그렇기에 segueDetailviewController에서 미리 프로퍼티를 생성해서 데이터를 이동시킬 필요성이 있다.

instance

앞에 방식과는 다르게 역으로 디테일 뷰에서 메인 뷰로 데이터를 넘겨줘서 메인뷰에 변화를 원할때 사용하는 방식이다.

ex) detail view에서 버튼을 눌러 main view에 글씨를 바꾼다.

새로운 viewcontroller를 생성한다. 그리고 버튼을 생성한다.

import UIKit

class InstanceDetailViewController: UIViewController {

    var mainVC: ViewController?    //존재하지 않을 수 있으니 옵셔널로 지정
    
    override func viewDidLoad() {
        super.viewDidLoad()

     
    }

    @IBAction func sendDataMainVc(_ sender: Any) {
        mainVC?.dataLabel.text = "some data"     //main쪽에 레이블을 변경한다.
    }
    

}

이렇게 코드를 구성하고,
main 쪽에서는 데이터를 받을 Label과 detailview로 넘어갈 버튼을 설정해준다.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
       
    }
    @IBOutlet weak var dataLabel: UILabel!    //드래그로 Label을 프로퍼티화 시킴
    
    
    @IBAction func moveToInstance(_ sender: Any) {
        let detailVC = InstanceDetailViewController(nibName: "InstanceDetailViewController", bundle: nil) //새로운 페이지로 넘어갈 버튼 함수 설정
        
        detailVC.mainVC = self //자신을 연결하게 해줌
        
        self.present(detailVC, animated:  true, completion: nil) //화면 이동
    }
    
}

delegate pattern (대리 위임)

delegate는 대신해서 데이터 전송을 도맡아 줄 프로토콜을 생성하는 새로운 class를 생성하고 그 프로토콜을 준수하는 함수의 형태로 데이터를 이동시킨다.

detail view를 생성하고 버튼을 하나 생성한다. 그리고 프로토콜을 생성한 뒤 데이터를 입력한다.

import UIKit

protocol DelegateDetailViewControllerDelegate: AnyObject {
    func passString(string: String)
}     //프로토콜 생성. 이 프로토콜이 데이터를 대신 전달하는 역할을 함

class DelegateDetailViewController: UIViewController {

    weak var delegate: DelegateDetailViewControllerDelegate? //delegate 프로퍼티 생성
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

  
    }


    
    @IBAction func passDataToMainVC(_ sender: Any) {
        delegate?.passString(string: "delegate pass data")   //원하는 데이터 입력
    } 

}

메인 뷰에서는 수신받을 데이터와 해당 프로토콜을 준수한다는 함수, 그리고 함수 부분의 바디를 기입해준다.

@IBAction func movetodele(_ sender: Any) {
        let detailVC = DelegateDetailViewController(nibName: "DelegateDetailViewController", bundle: nil)
        detailVC.delegate = self  //타입이 맞지  않으면 연결이 되지 않음. self는 프로토콜 타입 의미
        self.present(detailVC, animated: true, completion: nil)
    }
    
}

extension ViewController: DelegateDetailViewControllerDelegate {
    func passString(string: String) {
        self.dataLabel.text = string   //위 self가 이 부분을 준수함, 원하는 데이터 전송을 위해 이 부분을 수정하면 됨
    }
    
}

프로토콜을 준수한다고 클래스 부분에 기입해도 되지만 익스텐션으로 정의하는 편이 가시성이 좋다.

closure

호출하는 곳과 실제 구현부가 나뉜다.

새로운 클래스에 빈 클로저를 생성한 뒤 , 그 구현부를 메인 뷰에 기입한다.

detail view를 생성하고 버튼 하나를 만든다. 그리고 빈 클로저를 생성하고, 데이터를 입력한다.

import UIKit

class closuredetailViewController: UIViewController {

    var myClosure: ((String) -> Void)?  //빈 클로저 생성
    
    override func viewDidLoad() {
        super.viewDidLoad()

        
    }

    @IBAction func closurepassdata(_ sender: Any) {
        
        myClosure?("closure string")     //함수를 호출함. 비었을 수도 있기에 옵셔널
    }
    
}

메인쪽 에서는 다른것들과 다 똑같이 구현하되 클로저 구현부만 작성해주면 된다.

@IBAction func movetoclosure(_ sender: Any) {
        let detailVC = closuredetailViewController(nibName: "closuredetailViewController", bundle: nil)
        
        detailVC.myClosure = { str in   //str은 받는 파라미터
            self.dataLabel.text = str
        } //클로저를 메인 뷰에서 구현함
        
        self.present(detailVC, animated: true, completion: nil)
    }
}

delegate와 상당히 유사하다. 새로운 클래스에서 프로토콜을 만들어 이용하느냐 클래스를 만들어 이용하느냐의 차이이다.

Notification

호출부와 구현부가 따로 있다. 연결점을 안 만들어도 데이터를 주고 받아도 된다. 연결점 없는 클래스로 함수를 호출하거나 데이터를 주고 받고 싶을때 사용한다. detailVC 인스턴스를 생성하지 않아도 된다.

notification안에 있는 userinfo에 키값으로 저장되어있는 정보들을 활용해 데이터를 주고 받는다.

main view에서

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let notificationName = Notification.Name("sendSomeString")  //notification 이름 설정
        
        NotificationCenter.default.addObserver(self, selector: #selector(showsomeString), name: notificationName, object: nil) //#selector는 특별한 함수를 호출한다는 뜻,   
        
    }
    
   @objc func showsomeString(notification: Notification) {   //notification을 호출하는 함수
       if let str = notification.userInfo?["str"] as? String {   //userInfo안에 키밸류값이 저장되어있음 str은 키 값, 어떤 타입이 올지 알기 때문에 String 타입으로 캐스팅하는것이 안정적임
        self.dataLabel.text = str
    }
}

호출하는 새로운 클래스를 생성

import UIKit

class NotiDetailViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func notiAction(_ sender: Any) {   //버튼 액션 생성
        
        let notificationName = Notification.Name("sendSomeString")
        
        let strDic = ["str" : "noti string"]
        
        NotificationCenter.default.post(name: notificationName, object: nil, userInfo: strDic) //post 는  notification의 이름으로 호출
    }
    
}

0개의 댓글