[iOS] App Development

[iOS] 기본 개념 정리 #2 : UISlider, Struct VS Class, ViewController

ddgoori 2021. 10. 30. 16:08

* iOS 개인 프로젝트 시작 전 기본 개념 복귀 차원으로 내용을 정리합니다.

 

  1. UISlider
    1. 스토리보드에 있는 UISlider의 Value를 UILabel을 이용해 수치 보여주는 법
    2. 예상
      1. 우선 IBAction으로 끌어와서 UISlider의 이벤트가 일어날 때마다 UILabel.text에 슬라이더의 value를 대입 시키면 되지않을까?
      1. 슬라이더 IBAction으로 선언, Event타입, Type은 UISlider로하여 더 정확한 데이터 타입으로 받아올 수 있게 한다. Any는 어떤 데이터타입이든 다 됨
      2. sender는 해당 메소드를 호출하는 caller를 말함. 어떤 버튼 혹은 슬라이더가 불렀는지 func caller를 확인할 수 있음. 아래의 경우 UI슬라이더 이벤트발생시, 그 UISlider자체를 말함
      3. @IBAction func heightSliderChanged(_ sender: UISlider) {
        	print(sender.value)
        }
      4. UISlider를 누르고 우측의 attribute inspector를 보면 value값을 조절할 수 있다.
      5. sender. 누르고 아래 확인해보면 value가 있다.
      6. sender는 우리가 연결한 UISlider다.
      7. decimal places조절 -> String(format: "%.2f", sender.value)
      8. Int(sender.value) 로 decimal places(소수점)을 없앨 수도 있다.
      9. UILabel Outlet으로 property 선언 그리고 connected
      10. heightLabel.text 에 대입하면 됨! "\(height)m" 로 단위 설정가능
    3. 버튼 눌러서 값 계산하기
      1. 버튼 IBAction으로 뷰컨으로 끌고와서 Connect
      2. let height, let weight 등 계산해야하는 변수들은 다른 IBAction에 선언되어있다.
      3. 예상:
        1. 전역변수로 올려야하는 것인가? 
        2. ㅇㅇ 맞지만 slider를 바로 IBOutlet으로 빼서 그 변수의 value값을 가져오면 됨
      4. 답: UISlider의 value값으로 계산해야하니 heightSlider, weightSlider 를 IBOutlet으로 선언하고, 해당 변수들로 계산하면 됨
      5. @IBAction func calculatePressed(_ sender: UIButton) {
         // 버튼을 누르면 IBAction이 트리거 되지만, sender는 UIButton의 sender이며,
         // UISlider의 sender는 아니다. 그럼 어떻게 UISlider의 sender에 있는 value를
         // 여기서 계산할 수 있을까?
         
        }
    4. square number => pow사용하면 됨. 제곱하는 법 pow(height, 2). => height^m2와 같음
      1. 수학 계산할 때는 () brackets로 감싸서 계산순서 유의하기
    5. Swift Classes
      1. class MyClass {}. => 클래스명은 대문자로 시작하고, 파일명과 클래스명이 같은 것이 convention임
      2. class는 property, method로 구성된다.
      3. class에 선언하는건 blueprint와 같고
      4. 이제 사용할 곳에서 initialize를 하면 된다 let skeleton = Enemy() 이런식으로. 객체를 생성하는 것이다.
      5. skeleton.health  / skeleton.move() / skeleton.attack() 가능하고, let skeleton2 = Enemy() 등 청사진을 총해 만들 수 있음
      6. struct와 class는 비슷한데 뭐가 다른 걸까?
      7. class는 superClass로 부터 상속이 가능하다는 점이다.
      8. SuperClass 가 가진 methods, properties를 subClass가 가지고, subClass에 프로퍼티와 메소드를 추가할 수 있다.
      9. subClass 만드는 법
      10. class Dragon: Enemy { } => Enemy class를 상속 받음
      11. let dragon = Dragon() 으로 인스턴스를 생성하고,
      12. dragon.health() / dragon.move() 등 Enemy class가 가진 모든 것이 가능하다.
      13. class Dragon: Enemy {
      14.     var wingSpan  = 2 
      15.      func talk(speech: String) {
      16.         print("\(speech)")  
      17.      }
      18.             } 에 프로퍼티와 메소드를 추가해보자. 
      19. 이건 dragon class만 가능하다
      20. 하지만, superClass에서 받은 메소드를 SubClass에서 변경하고 싶다? => override로 사용
      21. override func move() {
      22. } 로 선언하면, Dragon class를 가진 객체는 move() 실행시 override한 함수를 실행한다.
      23. override func attack() {
      24.    super.attack()
      25.     print(" 드래곤 어택!")
      26. } 와 같이 선언하면 super.attack()를 통해서 Enemy 클래스가 가진 attack이 살행되고, 또 Dragon class에서 선언한 print( "드래곤어택!")도 실행이 된다. override해서 섭클래스에서 같은 명을 가진 메소드를 사용허고 싶지만, 수퍼의 메소드도 사용하고 싶으면 super. 를 이용해서 호출하면 된다. 
      27. NSObject
      28. UIResponder
      29. UIView
      30. UIControl
      31. UIButton
      32. 순으로 최상위에서 상속을 받아서 하는 것이다.
    6. Struct VS Class
      1. Struct 상속이 불가능
      2. 하지만 비슷하게 생겼다. 뭐가 다른 걸까?
      3. struct는 이니셜라이즈를 할필요가 없다. 
      4. class는 init이 필요하다. 초기값 설정이 필요한 것임
      5. 그래서 아래의 Enemy 객체를 만들 때, init에 파라미터가 있을 경우 값을 넘겨줘야 객체 생성이 된다.

      6. 최초 생성된 객체자체를 새로 생성한 변수에 넣으면? 그리고 최초 생성한 객체를 수정하면, 새로 생성한 변수가 최초 생성 객체를 가리키고 있으니깐 새로 복사한 그 객체의 프로퍼티도 과연 바뀔까?
        1. 예상: 바뀐다! 왜냐면 reference를 복사한 것이니깐?
        2. 정답: 바뀐다! classes are passed by reference이기 때문이다.
        3. let skeleton1 = Enemy(health: 100, attackStrength: 10)
        4. let skeleton2 = skeleton1
        5. skeleton1.takeDamage(amount: 10)
        6. print(skeleton2.health) // 90
        7. ====> skeleton1의 property에 데미지를 주었지만, skeleton2는 skeleton1의 레퍼런스이기 때문에 skeleton2에게도 영향이간다.
        8. 그러므로 새로운 object를 생성하고 싶으면 let skeleton2 = skeleton1을 하는게 아니라
        9. let skeleton2 = Enemy(health: 100, attackStrength: 10)으로 새로 Initialize 해야한다.
        10. 그럼 skeleton1의 property를 바꿔도 skeleton2는 완전 새로운 객체이므로 영향 가지 않는다.
        11. 그래서 struct보다 class가 struct보다 복잡해보인다.
        12. 심지어 만들어진 객체자체를 레퍼런스로 복사할 경우 
          1. skeleton1.takeDamage(amount: 10)
          2. skeleton1.takeDamage(amount: 10)
          3. skeleton2.takeDamage(amount: 10)
        13. 위 처럼 코드를 실행하고 
          1. print(skeleton1.health)
          2. print(skeleton2.health)
        14. 를 해보면 둘 다  70이 나온다. skeleton1 과 skeleton2는 같은 것을 가리키고 있다는 것이다.
        15. struct는 struct내부의 프로퍼티를 수정하는 function 생성시 mutating 키워드를 붙여야한다.
        16. 그리고 let 키워드를 사용해서 객체를 만들고 mutating func을 사용할 수 없다. var로 객체선언해야한다.
        17. struct의 경우 위와 같이 똑같이 12/13처럼 코드를 진행해도 skeleton1과 skeleton2는 완전 다르게 취급된다.
      7. class 와 struct의 큰 차이 정리 
        1. Structs are Passed by Value => 사진을 똑같이 복사한다는 것 
        2. Classes are Passed by Reference => 사진이 있는 URL을 알려줌 
      8. struct
        1. 상속불가능
        2. 불변 => 내부 프로퍼티 수정하려면 func에 mutating 키워드 선언 필요 
        3. passed by value
      9. class
        1. inheritance 상속가능
        2. passed by refrence
      10. Create Custom UIViewController Class
        1. 새로운 스크린이 생기면? 새로운 UIViewController와 연결해야한다.
        2. 파일명과, class명은 동일하게 가는 것이 Convention
        3. UIViewController상속 받기(superClass로)
        4. import Foundation같은 것을 프레임워크라고 부름
        5. Foundation은 Swift Programming Language의 대부분의 feature들 동작할 수 있게 해주고,
        6. UIkit는 UILabel, UISlider, UIViewController 등 UI로 시작하는건 UIKit 프레임워크에 포함되어있다.
        7. 그래서 UIKit를 Import해야 UIViewControllet를 상속받아서 사용할 수가 있다.
        8. UIKit가 Foundation을 포괄하는 개념이다. 더 상위 레벨임
        9. 이제 UILabel, UIColor, UIImageView 등을 이제 사용할 수 있다. 
          1. override func viewDidLoad() {
          2.          super.viewDidLoad()
          3. }.  => codes that triggered when our view loads up. 뷰가 로딩되면 실행되는 코드 
      11.  스토리보드 사용없이 프로그래밍으로 레이블 만들어보자.
        1. viewDidLoad안에 선언한다
        2. let label = UILabel()
        3. label.text = "Hello"
        4. label.frame = CGRect(x: 0, y:0, width: 100, height: 50)  // 위치와 사이즈조절
          1. labe.frame 을 설정하고 frame을 보면 CGRect 객체 타입이라고 나와있음
          2. 그래서 class를 통해 객체를 만드는 것처럼 CGRect로 객체를 생성함
        5. 이제 스크린에 올려야함. view.addSubview(label)
          1. View는 어떤 뷰컨트롤러의 백그라운드다 => 뷰컨트롤러를 생성하면 항상 View가 포함되어서 옴
          2. 그래서 view.addSubview(label) // addSubView를 사용하면 UIView데이터타입을 원하는데, UILabel은 UIView를 상속받으므로 UILable인 label을 넣어도, 가능한 것이다 UILabel이 곧 UIView이기 때문임
          3. view는 parent 고 addSubView는 child
      12. 첫번째 화면 뷰컨에서 두번째화면 보여주기 
        1. 첫번째 뷰컨에서 caculate UIButton이 눌러지면 세컨뷰로 보이도록 한다.
        2. 첫번째 뷰컨에서 IBAction func있는 쪽에서 동작하고 싶어하니 그 안에서 코드 작성하면 된다.
        3. class SecondViewController는 bluePrint다.
        4. 이걸 첫번째에서 object로 만든다. 
        5. let secondVC = SecondViewControllerI() 로 이니셜라이즈 한다. 
        6. self를 누르면 현재의 첫번째 뷰컨 current 뷰컨을 가리킨다. 그래서
        7. self.present(secondVC, animated: true, completion: nil)
        8. => 해석하면, 야 첫번째 ViewController(현재 코드 작성중인), present해라(sencondVC를!)
        9. 두번째 뷰컨에 대한 객체를 생성하고 그 객체를 present 메소드에 입력하여 두번째 뷰컨으로 이동하는 코드를 첫번째에서 작성하는 것이 핵심이다.
        10. completion은 애니메이션도 되고 present했을 때 아무것도 안해도 됨!이라는 뜻에서 nil을 적음
      13. 자 그렇다면 첫번째 뷰컨에서 만든 값들을 어떻게 두번째 뷰컨에 pass할 수 있을까?
        1. 우선 세컨뷰컨트롤러에서 var bmiValue 프로퍼티를 만들고
          1. second.bmiValue = bmi로 이동시키면 된다. 이때 포맷이문제면 String(format: "%.1f", bmi)로 포맷팅해주면된다.
        2. 결국 정리하면, 두번째에 뷰컨이 class니깐 그 안에 property를 만들고,
        3. 첫번째 뷰컨에서 두번째 뷰컨 class를 객체 object로 만들면, 당연히 그 두번째 뷰컨 클래스의 속성도 컨트롤 할수가 있다.
        4. 그렇게 첫번째 뷰컨에서 secondVC.bmiValue로 두번째 뷰컨에 선언한 프로퍼티를 불러와서,
        5. 첫번째 뷰컨에서 계산해놓은 bmi를 넘겨주면 된다. secondVC.bmiValue = String(format: "%.1f", bmi)