본 내용은 스윗한 SwiftUI 책에 있는 예제를 공부한 내용입니다.
해당 예제들에 대한 저작권은 BJpublic 에 있습니다.

전체적으로 훑어보기

VStack(spacing: 30) {  // 세로 방향으로 뷰를 배열하는 컨테이너 뷰
            Text("폰트와 굵기 설정")
                .font(.title) // 폰트 설정
                .fontWeight(.black) // 폰트 굵기
            
            Text("글자색은 foreground, 배경은 background")
                .foregroundColor(.white)
                .padding()      // 텍스트 주변 여백 설정
                .background(Color.blue)
            
            Text("커스텀 폰트, 볼드체, 이탤릭체, 밑줄, 취소선")
                .font(.custom("Menlo", size: 16))
                .bold()
                .italic()
                .underline()
                .strikethrough()
            
            Text("라인 수 제한과 \\n 텍스트 정렬 기능입니다. \\n 이건 안 보입니다.")
                .lineLimit(2)
                .multilineTextAlignment(.trailing) // 다중행 문자열의 정렬 방식 지정
                .fixedSize() // 주어진 공간의 크기가 작아도 텍스트를 새략하지 않고 표현하도록 설정
            
            // 2개 이상의 텍스트를 하나로 묶어서 동시에 적용
            (Text("자간과 기준선").kerning(8) //자간
             + Text(" 조정도 쉽게 가능합니다.").baselineOffset(8)) // 기준선
            .font(.system(size: 16))

 

Text 수식어 적용의 순서의 중요성

  • Text 와 View 의 font, foregroundColor 의 정의
extention Text {
	// Text와 View 모두 있는 수식어
	public func font(_ font: Font?) -> Text
	public func foregroundColor(_ color: Color?) -> Text
	
	// Text에만 있는 수식어
	public func bold() -> Text
	public func italic() -> Text
}

extention View {
	// Text와 View 모두 있는 수식어
	public func font(_ font: Font?) -> some View
	public func foregroundColor(_ color: Color?) -> some View

	// View 에만 있는 수식어
	public func padding(
		_ edges: Edge.Set = .all,
		_ length: CGFloat? = nil
	) -> some View
}
  • Text에도, View에도 font 와 foregrountColor 가 정의되어 있다
  • 단, 반환 타입이 다름을 주시하자
  • View 에만 있거나, Text에만 있는 경우 순서가 중요하다

잘못된 순서로인한 변화 예제

Text("SwiftUi")
	.font(.title) // Text - 호출자의 타입이 Text
	.bold()       // Text
	.padding()    // View - padding 수식어 호출 이후로는 Text가 아닌 View 반환

Text("SwiftUi")
	.bold()       // Text
	.padding()    // View
	.font(.title) // View - 동일한 font 수식어를 호출해도 호출자에 따라 반환타입 다름

Text("SwiftUi")
	.padding()    // View
	.bold()       // 컴파일 오류 - View에는 bold 가 정의되어 있지 않기 때문
	.font(.title) 

Text("SwiftUi")
	.font(.title) // View
	.padding()    // View
	.bold()       // 컴파일 오류 - View에는 bold 가 정의되어 있지 않기 때문

잘못된 순서로 인한 변화 예제2

// 1번
Text("🐶🐱🐭🐹🐼").font(.largeTitle)
	.background(Color.yellow) // 배경 색상지정이 Text 크기에 따라 우선 적용
	.padding()                // 배경 색상이 적용된 Text에 padding 적용

// 2번
Text("🐶🐱🐭🐹🐼").font(.largeTitle)
	.padding()                // Text에 Padding 을 우선 적용!
	.background(Color.yellow) // padding 이 적용된 부분에 배경색상 지정

  • 1번

  • 2번

  • 보이는 것과 같이 순서에 따라 View 자체가 어떻게 그려질지 보여준다
  • 이처럼, SwiftUI에서는 수식어의 순서에 따라 오류가 날수도, 보여지는 뷰가 다를수도 있음을 유의하자

 

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)

 

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

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

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 의 개념을 알아야한다!

MVC 패턴의 개요

Model - View - Controller 구조로 이루어진 디자인 패턴을 의미한다.

 

Model : 앱의 데이터 또는 비즈니스 로직을 소유 ( 데이터의 변수 및 상수 , 데이터 값의 계산 등을 수행 )

View : 사용자에게 데이터 또는 UI 를 보여주는 역할을 수행 ( Storyboard 가 View 에 포함 )

Controller : Model  View 의 중간에서 View로부터 사용자의 이벤트를 감지하고, 해당 이벤트 처리를 Model에게 지시하며, Model의 계산된 데이터를 받아 View에게 전달하는 역할을 수행 ( TableViewController , ViewController, 등등.. )

 

Model - View - Controller 는 각각 어떻게 소통을 하나?

음식점을 예로 들어보자

1. 손님은 음식을 주문하기 위해 음식점의 홀에 있는 종업원에게 음식을 주문한다.
2. 음식 주문을 받은 종업원은 주방장에게 주문 내역을 보여준다.
3. 주방장은 주문내역을 보며 음식을 조리하여 조리가 완료되면 종업원을 다시 부른다.
4. 종업원은 해당 음식을 손님에게 제공한다.
5. 만약, 음식의 맛이 이상하거나 추가적인 조리를 원한다면 손님은 종업원을 호출한다.
6. 종업원은 손님의 요구사항을 적어 주방장에게 전달한다.
7. 주방장은 손님의 요구사항대로 음식을 재 조리하여 종업원을 부른다.
8. 종업원은 손님에게 재 조리된 음식을 제공한다.

자! 이제 종업원을 Controller로, 손님을 View로, 주방장을 Model 로 생각해보자

손님(View)은 요구사항을 전달하기 위해 종업원(Controller)에게 요구사항을 알려준다.

종업원(Controller)는 해당 요구사항을 주방장(Model)에게 전달한다.

주방장(Model)은 요구사항대로 조리가 되면 종업원(Controller)에게 음식을 전달한다.

종업원(Controller)주방장(Model)로부터 받은 음식을 손님(View)에게 전달한다.

즉, View  Model 은 직접적으로 소통할수 없고, Controller를 통해서만 소통을 할수 있다.

 

아주 간단한 MVC 패턴 적용 예시

TableView 를 사용해 이름들을 나열하는 화면을 그려보자!

 

일단 나 같은 경우, 공부를 해가는 과정이지만 Udemy - 안젤라유님의 강의를 듣고 최대한 습관화 하려고 노력하고있다.

이렇게 Model - View - Controller 를 구분하여 그룹으로 묶어두면 MVC패턴에 익숙해지는데 도움이 될거같아서...


Model

Name 구조체 먼저 선언해주고


View

View는 Storyboard 파일도 포함되지만, 이렇게 일부 Cell을 떼어내어 View로 구성할수 도 있다!


Controller

해당 부분을 보면 알겠지만, Model에 선언된 구조체를 Controller가 그 내용을 담고있으며,
View의 변화 ( TableView의 DataSource ) 에 따라 해당 값을 Controller가 전달한다.

 

이를 실행해보면

이와 같이 TableView 로 각 이름 값 들을 표시하는걸 볼 수 있다!

물론 해당 예시가 완벽한 MVC 패턴을 그리고 있진 않지만 대략적으로 이렇게 사용된다고 이해하면 될 것 같다!

 

 

MVC 패턴의 장점

  • 다른 패턴들에 비해 비교적 적은 코드량을 갖고있다
  • Apple 에서 SwiftUI가 나오기 전, 기본적으로 채택하고 있던 패턴요소이다! ( 하지만,, 현재는 MVVM 패턴을 강조하고 있다... MVVM 패턴은 아직까진 숙지가 되지 않아 이해하게 되면 블로그에 올릴 생각! )

 

MVC 패턴의 단점

  • 모든 일을 Controller가 맡아서 하고있다, 즉 Cotroller가 너무 많은 업무를 담당하게 된다
  • Controller의 코드가 너무 길어질 수 밖에 없는 구조! → Controller의 내부 구조가 복잡해지기 쉽다
  • 프로젝트의 규모가 커질수록 유지보수 하기에 어려움이 있다! ( 사실 아직까지 큰 프로젝트를 한적이 없어서 이 말이 잘 이해가 가지 않는다... 나중에 하게 된다면 몸소 느껴봐야겠다! )

+ Recent posts