Tabview

  • Tab을 만들어 각각의 view 마다 다른 화면을 보여주는것
Tabview {
	View()
		.tabItem {
		//- tabItem 의 내용 이미지, 이름 등을 설정
	}
}
  • tabItem에는 Label , Text, Image 만 허용하고, Button 과 같은 컴포넌트가 포함되면 해당 뷰는 빈 페이지로 로드

Data

  • Model 개념의 구조체로 데이터의 ‘청사진' 을 구성
struct Info {
    let image: String
    let name: String
    let story: String
    let hobbies: [String]
    let foods: [String]
    let colors: [Color]
    let funFacts: [String]
}
  • 해당 데이터의 모델 구성하면, 앱의 전반적인 곳에서 해당 모델을 사용할 수 있음
let information = Info(
    image: "Placeholder",
    name: "My Name",
    story: "A story can be about anything you can dream up. There are no right answers, there is no one else.\\n\\nNeed some inspiration?\\n• 🐶🐱🛶️🎭🎤🎧🎸\\n• 🏄‍♀️🚵‍♀️🚴‍♀️⛵️🥾🏂⛷📚\\n• ✍️🥖☕️🏋️‍♂️🚲🧗‍♀️ ",
    hobbies: ["bicycle", "ticket.fill", "book.fill"],
    foods: ["🥐", "🌮", "🍣"],
    colors: [Color.blue, Color.purple, Color.pink],
    funFacts: [
        "The femur is the longest and largest bone in the human body.",
        "The moon is 238,900 miles away.",
        "Prince’s last name was Nelson.",
        "503 new species were discovered in 2020.",
        "Ice is 9 percent less dense than liquid water.",
        "You can spell every number up to 1,000 without using the letter A.\\n\\n...one, two, three, four...ninety-nine...nine hundred ninety-nine 🧐",
        "A collection of hippos is called a bloat.",
        "White sand beaches are made of parrotfish poop.",
    ]
)

View 의 구성

  • VStack 은 컴포넌트 들을 수직으로 정렬
  • Text 를 사용해서 글을 표시할수 있으며 .font / .fontWeight 등의 옵션으로 글을 커스텀 할수 있음
  • padding 을 사용하여 다른 뷰와의 거리를 띄울수 있음
  • Image 를 사용해서 이미지를 추가 할수 있다
    • .resizable() 을 사용하여 이미지의 크기를 조절 할 수 있다
    • .aspectRatio(contentMode: .fit) 을 사용하여 이미지의 종횡비를 지정할 수 있다.
    • .cornerRadius() 를 사용하면 이미지의 모서리를 둥글게 할수 있다.
Imgae("이미지 내용")
  • ScrollView
    • ScrollView 를 구성 하려면 ScrollView { ... } 로 감싸면 된다 
ScrollView { Text(information.story) .font(.body) .padding() }
  • HStack 은 컴포넌트 들을 수평 으로 정렬
  • ForEach 구문을 사용하여 뷰를 구성할 수 있다.
    • id: \.self 를 통해 고유 값을 설정한다 ( 인덱스의 개념! )
ForEach(information.hobbies, id: \\.self) { hobby in 
	Image(systemName: hobby) 
    	.resizable() 
        .frame(maxWidth: 80, maxHeight: 60) 
}
  • @State 를 사용하면 내용이 바뀔때마다 view 를 새로 그린다
  • Button 클로저 아래에 여러 설정값으로 버튼의 모양을 변경할 수 있다
Button("Show Random Fact") { 
	funFact = information.funFacts.randomElement()! 
    } 
    .padding() 
    .background(Color.cyan) 
    .cornerRadius(20)

 

SwiftUI에서 어떠한 view는 View 프로토콜을 준수해야 한다

protocol View {
	associatedtype Body: View
    	var body: Self.Body { get }
}

 

=> 프로토콜을 보면 알 수 있듯, 필수로 구현해 줘야 하는것은 연산프로퍼티인 읽기 전용 body 임을 알 수 있음

 

Text, Image, Color, Stack, Group, GeometryReader 등의 컨텐츠나 컨테이너 뷰에는 더이상 Body를 호출하지 않게 Never 타입을 사용

typealias Body = Never

 

1. 선언형

명령형 vs 선언형

명령형

UIKit 의 경우 명령형 선언으로써, 방법(How)에 초점을 두어 코드를 서술한다.

1) 버튼을 생성

2) 버튼의 제목 설정

3) 버튼 제목의 색상 설정

4) 폰트 지정

5) 버튼 클릭시 호출할 메서드 지정

6) 루트 뷰에 자식 뷰를 추가

7) 버튼을 화면 가운데로 배치

 

선언형

SwiftUI의 경우 선언형 선언으로써, 무엇(What)에 초첨을 두어 코드를 서술

Button(action: {
	print("Hello, SwiftUI")
}) {
	Text("SwiftUI")
    	.font(.title)
        .foregroundColor(.black)
}

=> 버튼을 생성하는데 버튼의 글자는 "SwiftUI" 이고, 폰트는 title, 색상은 검정색, 클릭했을때는 "Hello, SwiftUI"를 출력해줘

 

2. 자동화

=> 가능한 많은 기능이 자동으로 수행될 수 있게 제공하는것

자동화를 통하여 화면에 뷰를 배치할 오토 레이아웃 코드를 제거하고 최소한의 설명으로 적용할 수 있게 되었음

3.  조합

=> 큰 뷰를 하나의 기능을 가진 작은 뷰들로 잘게 나누거나 각각의 뷰를 조합해 원하는 뷰를 쉽게 만들어 낼수 있게 지원

4. 일관성

=> 데이터와 동기화되어 일관성 있게 보여줘야함

1) 데이터가 변경되면 그에 맞게 UI도 변경되어야함

2) 데이터가 변경되는 즉시 UI도 자동 갱신

3) 뷰의 특정 상태를 저장할 State / 모델 객첼의 변화를 관찰할 ObservableObject 로 인하여 데이터의 변화를 감지, UI 갱신

 

이제부터 습관화 하기로 했다..

무언갈 찾아볼때 순서를..

1. 공식문서

2. 이해 안된 내용 + 추가적인 내용에 대해서 구글링

 

결론.. 공식문서 짱짱..

 

1. 공식문서 훑어보기

1) Frame

The frame rectangle, which describes the view’s location and size in its superview’s coordinate system.

-> 슈퍼뷰 를 기준으로 위치 및 크기를 정의

강조한 부분을 잘 기억해야한다! 기준점이 자신보다 한단계 위의 뷰인 슈퍼뷰 이다!

var frame: CGRect { get set }

2) Bounds

The bounds rectangle describing the item’s location and size in its own coordinate system.

-> 자기 자신을 기준으로 위치 및 크기를 정의

강조한 부분을 잘 보자! 기준점이 자기 자신이다!

var bounds: CGRect { get }

 

💡CGRect는 직사각형의 위치와 치수를 포함하는 구조체 이며, 해당 구조는 

public struct CGRect {

    public var origin: CGPoint
    // 위치 정보

    public var size: CGSize
    // 크기 정보

    public init()

    public init(origin: CGPoint, size: CGSize)
}
/*

	...
	...
	...
	기타 확장 기능들이 구현되어 있음 
*/

로 정의되어 있습니다.. 이부분은 Code로 앱을 구현할때 사용하게 되는데

제가 아직 스토리보드 밖에 다룰줄을 몰라서.. 알게되면 포스팅 하겠슴다ㅎㅎ..

 

다시 본론으로 돌아가서

자! 공식문서만 봐도 차이점을 바로 알아버렸다 ^_-

간단하게 차이점을 이야기 하자면..

 

Frame 은 기준점이 슈퍼뷰에서부터 위치 및 크기를 지정하게 되는 반면,

Bounds 는 기준점이 자기 자신으로부터 위치 및 크기를 지정하게 된다!

 

 

로 끝내기엔 뭔가.. 이해가 되지 않으니 예시로 살펴 봐야겠다..

 

예시로 살펴보기

1. 먼저 frame 이다, 분홍색(빨간색) View가 superview고, 파란색 뷰가 frame을 적용한 뷰이다,

코드 를 살펴보자

class ViewController: UIViewController {
    @IBOutlet var superView: UIView!
    
    var view1 = UIView()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        view1.frame = CGRect(origin: CGPoint(x: 50, y: 50), size: CGSize(width: 300, height: 300))
        view1.backgroundColor = .blue
        superView.addSubview(view1)
        superView.backgroundColor = .systemPink
    }
}

위치 정보를 (50.50) 으로 주었고, 크기 정보를 (300 * 300) 으로 설정했다.

우리는 위에서 슈퍼뷰기준 으로 위치와 크기를 나타낸다고 배웠다

각 수치값을 표현다면 위와 같이 될것이다 

분홍배경의 슈퍼뷰 로 부터 50.50 만큼 떨어진 거리에 300 * 300 크기의 뷰를 그려준것이다.

 

2. Bounds 를 살펴보자!

배경에 대한 설명은 같고, 위 코드에서 frame 을 bounds로 변경하여 적용하였다!

@IBOutlet var superView: UIView!
    
    var view1 = UIView()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        view1.bounds = CGRect(origin: CGPoint(x: 50, y: 50), size: CGSize(width: 300, height: 300))
        view1.backgroundColor = .blue
        superView.addSubview(view1)
        superView.backgroundColor = .systemPink
    }

 

뭔가 이상하다..

frame 같은 경우는 어쩌면 자연스럽게 저렇게 되는구나 를 알수있는데

bounds 는 당최 어떻게 동작하는지를 모르겠다..

300 * 300 크기의 뷰를 (50.50) 위치에 놓았는데 어쩐지 왼쪽상단으로 쭈욱.. 올라갔다

기준점이 자기 자신 이란건 알겠는데 왜 저렇게 되는걸까?

 

이해하기가 좀 어려워서 zeddios 님의 블로그를 열심히 읽어 보았다!

그림하나로 저렇게 되는 이유가 종결된다!

정리하자면 bounds를 변경하는 것은, 변경된(설정된) 위치에서 내 자신의 View 를 다시 그려줘! 라고 하는것이다!

아래 그림을 보면 더 잘 이해가 갈것이다!

 

bounds의 origin(위치정보) 가 변경될때마다, 이미지 자체가 움직이를것이 아닌 

아이폰에 표시되는 view 자체의 위치가 이동된다!

 

이렇게..!!

 

이것이 만약 frame 이였다면. 상위뷰 기준으로 이동하기 때문에

 

이렇게 표시된다!!!

 

해당 개념은 직접 코드로 구현해보고 익숙해져야 더 깊이 이해가 갈것같다!

(특히 bounds..)

 

 

 

참고 사이트 : https://zeddios.tistory.com/203

iOS 공부하면서 스토리보드에서 컴포넌트를 끌어다 놓을때

strong / weak 의 연결을 하는것을 알게되었고 이게 무슨의미인가.. 싶었지만

그냥 weak으로 해라! 라고만해서 그 의미에 대해 깊게 파악해보진 않았던거 같다..

그래서 이 강한참조 / 약한참조 의 근본(?)인 ARC 를 공부해보쟈!

 

What is ARC?

ARC 는 Automatic Reference Counting의 약자다!

ARC는 메모리를 스스로 관리하는 기능을 수행한다 일반적으로 Swift가 자동으로 ARC를 이용해 메모리를 관리하기 때문에 사용자는 메모리는 어떻게 처리해야하는지 깊게 생각할 필요는 없다고.. (공식문서에 써있다..)

💡 Objective-C 에서는 MRC 라고 하는 수동 메모리 관리 기법을 사용하였다, Objective-C 에서는 수동으로 관리해야 하기 때문에
retain / release 를 사용해 사용수의 증가/감소 를 확인하여 메모리를 관리하였는데 ARC 로 넘어오면서 이걸 자동으로 해준다! 그래서 프로그래머는 더이상 메모리관리에 관한 코드를 따로 작성하지 않아도 된다! 

그래서 ARC 어떻게 동작하는데?

Class 의 인스턴스가 생성될때마다, ARC는 해당 인스턴스에 대한 정보를 저장하는 Memory 할당하게 된다!

이 메모리는 당연히 인스턴스의 method, property 의 값 등의 정보를 가지고있다!

 

그리고 해당 인스턴스가 더이상 필요가 없어지면 해당 메모리를 확보하여 다른 목적으로 사용가능케한다!!

그러니까.. 한마디로 요약하면 “ Class가 필요 없어지면 해당 메모리를 해제함

 

그럼.. Class가 필요한지 필요없는지 어떻게 판단하게될까..? 이름에서 유추할수 있다!

해당 Class의 인스턴스가 참조되고 있는 수를 세어 가지고 있다!

이 수를 세기위해 클래스 인스턴스를 할당할 때마다 강력한(Strong) 참조를 하게되며, 이 강력한 참조가 유지되는 동안은 할당 해제를 허용하지 않게된다!

백문이 불여일견 직접 예시로 살펴보자

class OS {
    let kind: String
    
    // 초기화
    init(kind: String) {
        self.kind = kind
        print("[인스턴스 생성] 현재 운영체제는 \\(kind)")
    }
    
    // 소멸자
    deinit {
        print("[인스턴스 할당 해제] \\(kind) 운영체제 종료")
    }
}

일단.. Class를 맹글어 주고..

var os1: OS?
var os2: OS?
var os3: OS?

이렇게 하면 인스턴스가 생성이!!!!

안됩니다.

예 안돼요..

 

Optional 로 선언되어 있어서 기본적으로 nil 값이 할당되기 때문에 class 인스턴스를 할당하지 않아요!

os1 = OS(kind: "MacOS")
// [인스턴스 생성] 현재 운영체제는 MacOS

이제야 비로소 인스턴스가 할당되었습니다! 그러면 ARC에 의해 강한 참조로 os1 변수가 counting 될거에요!

os2 = os1
os3 = os1

이렇게 선언하면 강한참조로 counting이 2개더 더해지겠죠?!

 

자 그럼 할당을 해재해 볼거에요 우리는 할당을 해제하면서

print("[인스턴스 할당 해제] \\(kind) 운영체제 종료") 구문이 수행되기를 원합니다!

os1 = nil
os2 = nil

2개의 변수를 할당 해제했음에도 deinit 구문이 수행되지 않습니다.. 왜일까요?!

ARC에 의해 os3 에대한 reference count 를 가지고 있기 때문입니다!!

os3 = nil
// 인스턴스 할당 해제] MacOS 운영체제 종료

비로소 참조 수가 0이 되면서 deinit 이 수행된다!!

 

하지만 ARC를 사용하면서 메모리 누수 가 발생하는 상황이 반드시 나온다!

이를 방지하기 위해서 우리는 weak  unowned 의 개념을 알아야한다!

+ Recent posts