🍎 IOS/SwiftUI

SwiftUI 강의 리뷰 - CS193p 2편을 보고...

콩리또 2022. 5. 23. 20:14

 

 

 

안녕하세요 콩리또입니다.

요즘 너무 더워지고 있어서 여름이 온게 실감나네요..😞

덥고 힘들어도 모두 지치지말고 열코딩해봅시당...

저번에 포스팅한 Stanford University's course CS193p의 2편을 보고 왔는데요

이번 강의에서 배운것들을 정리해보도록하겠습니다.

보시고 틀린게 있다면 댓글부탁드려요~

그럼 시작하겠습니다!

 


프리뷰에서 다크모드 보는 법

여러분은 프리뷰에서 다크모드를 볼 수 있다는 사실 알고 계셨나요?

정말 간단하게 모디파이어 하나만 추가해서 프리뷰에서 다크모드를 볼 수 있습니다!

 

PreviewProvider에서 View .preferredColorScheme(.dark) 라는

ViewModifier 주면 바로 다크모드 프리뷰가 생성됩니다!

라이트 모드랑 같이 띄우고 본다면 더 편하게 코딩할 수 있겟죠?

프리뷰에서 라이트모드, 다크모드 함께 보기

 


 

배경색과 외곽선이 있는 사각형 만들기

배경색과 외곽선이 있는 사각형을 만들어 보신적있으신가요?

혹시 사각형 하나에 fill, stroke 각 Modifier를 줘서 만들면되지~ 라고 생각하고 계신가요?

저도 그렇게 생각했었는데요...  그렇지 않다는 놀라운 사실을 배웠습니다!

배경색과 외곽선이 있는 사각형은 한 개의 사각형으로는 만들 수 없다.. 는 사실!

 

왼쪽처럼 하나의 사각형에 fill과 stroke 를 주면 오류가 뜹니다. (심지어 fill의 색상이 stroke에 적용되는 띠용한 결과가..)

우쪽처럼 같은 도형을 하나 더 만들어서 각각 Modifier를 따로 줘야 하나의 배경&외곽선이 있는 사각형을 만들수 있습니다..

(왼) fill, stroke을 같이주자 오류뜸, (우) 도형을 2개를 만들어 각각 fill, stroke를 줌

 


 

값이 없는 변수는 있을 수가 없써~!!🙃

당연한 이야기지만 변수에는 값이 없으면 안됩니다.

변수는 생성되는 시점부터 값이 있어야 하고, 없다면 어디선가에는 꼭 값이 설정되어야합니다.

(옵셔널은 조금 특별한 친구인데,  마치 옵셔널은 값이 없는것처럼 보이지만  값이 없는것이 아니라 아직 설정되지않앗을뿐...)

 

이번 강의에서는 Struct로 CardView를  따로 빼고 

카드 뒤집기를 위해 isFaceUp이라는 변수를 Bool 타입으로 주고 초기값을 주지 않은 채

ContentView에 데려왔습니다.

 

ContentView에 데려온 CardView에 오류가 뜨는 모습..

그러자 ContentView에 'Missing argument for parameter ‘isFaceUp’ in call' 이라는 오류가 나타나는데

이 오류는 CardView를 호출 ‘isFaceUp’ 매개변수에 대한 인수 누락했다는 의미입니다.

한마디로 'CardView의 isFaceUp에 값이없으니 정해조!!😤' 라고 이해하시면 될 것 같아요~

 

(좌) 변수에 초기값 설정 (우) 불러온 View에서 값이 없는 변수에 값을 준다.

 

이런 경우!! 왼쪽처럼 해당 변수에 초기값을 설정해주거나 

오른쪽의 첫번째처럼 호출한 View뒤의 ()안에 해당 변수에 들어가는 값을 주면 됩니다.

 

만약 변수 선언 시 초기값도 설정하고, 그 View를 다른 Struct에서 호출 할 때,

View뒤의 ()에 다른 값을 지정해준다면 어떤 값으로 보이게 될까요??

정답은 바로~~! Struct에서 호출 할 때 View뒤의 ()에 넣어준 값으로 나타난다고 하네용

 

 


 

지역변수 설정해서 코드 정리하기

다들 코드 짜다가 같은 요소들이 반복해서 들어가는 경험 해보셨나요?

같은 요소들이 반복하긴하는데 이걸 Struct로 빼기는 오바고 

괜히 코드가 길어져서 짜증나는 경험 다들 있을거라고 생각됩니다!

 

그럴때는 지역변수로 따로 빼서 코드를 간결하게 정리할 수 있습니다!

예시를 보여드리면 아까 위에서 배경&외곽선이 있는 사각형을 만들 때

RoundedRectangle(cornerRadius: 20)라는 코드가 반복되는것을 볼 수 있습니다.

 

ZStack안에 shape라는 변수로 반복되는 RoundedRectangle(cornerRadius: 20)를 넣어주고

아래 코드에 RoundedRectangle(cornerRadius: 20) 대신 shape를 사용하면 더 깔끔한 코드가 완성된것을 확인할 수 있습니다.

지역 변수를 설정해서 깔끔한 코드를 작성해보쟈

 

그리고 관련해서 다들 알 것 같지만

그래도 한번 더 참고하면 좋을 내용을 접어놓겟습니다!😎

더보기

1. 변수와 상수

방금 예시를 보면 RoundedRectangle(cornerRadius: 20)를 변수(var)가 아닌

상수(let)로 선언했는데요? 과연 변수(var)과 상수(let)의 차이점은 무엇일까요?

 

바로 선언 후 값이 변하는지 여부에 따라 var을 쓰냐 let을 쓰냐가 달라지게 됩니다.

변수 선언 후 값이 중간에 변할 수 있으면 var, 선언 후 값이 변하지 않으면 let을 사용하면 됩니다.

RoundedRectangle의 경우 값이 변할 일이 없기 때문에 상수(let)으로 선언해준 것 입니다.

var로 선언하자 경고의 문구가 나옴

 

2. 변수와 타입

근데 여기서 궁금증이 생기시는 분이 계실텐데...

아까 CardView의 ‘isFaceUp’ 같은 경우는 변수의 타입을 적어줬는데

지금 선언한 shape의 경우는 따로 타입을 적지않아도 오류가 나지않네요?🤔

 

폴 겨수님께서 말하길 Swift는 반복해서 같은 말을 작성하는 것을 좋아하지않아 Swfit가 타입을 추론해준다고 합니다.

타입을 추론한다의 의미는 Swift가 해당 변수의 값이 어떤 타입인지 볼 수 있다는 것을 의미한다고 하네요

( inferring a type just means that Swift are going to look at the context that this thing is in) 

 

 고로, Swift가 타입을 유추할 있는 값이면 변수에 대한 타입을 따로 작성하지 않아도됩니다!

 


 

.onTapGesture의 인수는 함수!

onTapGesture는 어떤 View를 탭했을 때 action이 발생하는 상황에서 사용하는 Modifier 입니다.

onTapGesture의 인수는 함수 값이 들어가는데 이게 저번에 포스팅한 함수형 프로그래밍의 특징 중 하나였죠?!

아래 간단한 onTapGesture의 설명을 보시면 작성법을 이해하기 쉬우실 거에요

onTapGesture의 설명

onTapGesture의 파라미터는 countperform이 있습니다.

  • count 
    • Int값, 액션을 실행할 탭의 횟수 (기본값이 1이기 탭을 2회 이상 할 때 부터 작성)
  • perform
    • Void(반환값이 없는 함수),  탭을 했을때 나타나는 액션을 작성
    • perform에 들어가는 함수는 Void => View를 반환하지않는 일반함수, 아무것도 반환하지않음!

 


 

 

@state

신나게 CardView를 만들던 중 갑자기 이런 오류가 뜨게 됩니다..!

카드의 앞,뒷면도 만들엇고 탭하면 취할 액션까지 넣어줬는데

이 오류는 뭐죠?!

 

'self'는 변경할 수 없습니다ㅠㅜ

 

onTapGesture에 넣은 변수가 바뀌는 부분이 문제였군요? 

Cannot assign to property: 'self' is immutable (속성에 할당할 수 없습니다. 'self'는 변경할 수 없습니다.)

오류를 보면 'self'는 변경할 수 없습니다.라고 하고 있습니다.

그럼 여기서 self가 의미하는건 무엇이고 왜 이런 오류가 발생하는 걸까요?

 

일단 self는 struct 전체 코드를 의미합니다.

저 오류가 발생하는 이유는 SwiftUI의 모든 View는 변경 할 수 없고 수정할 수 없기 때문에 발생합니다.

var 선언한 변수라고 할지라도 한번 값이 정해지면 바로 변경할 방법은 없습니다.

 

띠용? 그럼 터치나 사용자 액션에 의해 변하는 UI 어떻게 만들어야할까요??

이럴때 바로 @state를 사용해야합니다. 사용자 액션에 의해 값이 바뀌는 변수(var)앞에 @state를 적어주면

SwiftUI가 값이 변하는 것을 감지해서 그때마다 해당 View를 다시 빌드하게되는거죠

(로직의 변경사항의 반영하고 이전 보기를 대체하기 위해 view를 다시 그림)

 

폴 교수님은 @state는 메모리 어딘가에 있는 변수에 대한 포인터라고 하셨는데요

( it’s actually a pointer to some Boolean someWhere else, somewhere in memory)

SwiftUI는 그걸 보고 값이 변하는것을 감지한다고 하셨습니다.

(사실 이 부분 잘 해석하지 못해서 확실한건지는...ㅎㅎ)

 

그래서 아까 오류가 났던 struct의 isFaceUp 변수 앞에 @state를 붙여주니

오류도 해결되고 카드도 잘 뒤집히는것을 확인할 수 있습니다.

 

isFaceUp 변수 앞에 @state를 붙여주니 오류해결~!

 

+ 여담

더보기

여담이긴한테 폴교수님은 실제 빌드를 시작할때 @state 많이 사용하지않을 것이라고 하시더라고요?

그 이유에 대해 @state 대부분 일시적인 상태일 경우에만 사용한다는 식으로 말씀을 하셨습니다.

 

  • 그래그 또는 핀치 멀티 터치하는 중이고 진행되는 동안 일부 상태를 유지하고 드래그가 끝나면 상태를 신경 쓰지 않는 경우
  • View가 표시되는 방식에만 영향을 미치는 상태에 대해서만 사용가능

 

@state를 남발하던 저로써는 좀 띠용한 정보였고,

렇다면 앞으로 어떤 방식으로 대체할 수 있는지..? 남은 강의들에 대한 기대가 높아지네요ㅎㅎ

 


 

설명 보는 법

모두 아시는 사실이겠지만 혹시나 모르시는 분이 계실까봐 알려드리는 사실!

코딩 중 모르는 코드가 나와 설명을 보고 싶은 적이 있으셨나요? 그럴때마다 구글링을 하시고 계셨나요?

이때! 빠르게 설명 팝업을 띄울 수 있는 꿀팁!

 

option(Alt) 키를 누르고 설명을 보고 싶은 코드를 클릭하면 해당 코드에 대한 설명 팝업이 나와

검색 없이 빠르게 해당 코드는 뭐고 어떻게 사용하는건지 확인할 수 있습니다.

또 팝업 하단에  Open in Developer Documentation 링크를 통해 개발자문서로 이동할 수 있습니다!

option 키를 누르고 코드 선택 시 설명 팝업이 뜸

 


 

배열(Array)을 만들어 봅시당!

[ ](대괄호)는 배열(Array)를 의미하죠?

배열은 body 바깥에 변수로 정의해서 사용할 수 있습니다.

또한 string으로 배열을 만들때는 따로 타입을 안써줘도 되는데

이는 위에서 말씀드린 것과 같이 SwiftUI가 타입을 추론해주기 때문입니다!

고로 SwiftUI가 타입을 추론할 수 있는 상황에서는 Array의 타입을 생략할 수 있다는 점 기억해주세요!

 

emojis 배열과 안의 요소들

 

만약 저 코드에서 String 타입을 쓰고 싶다면 아래 이미지와 같이 작성해주시면 되는데

굳이 코드를  복잡하게 쓸 필요는 없으니 참고만 해주세요!

 

둘 다 Array에 String 타입이 들어가는 걸 의미

 


 

ForEach에 "id: \.self"를 쓰는 이유

다들 ForEach를 사용해보셨나요?

저는 보통 Array를 요소들을 View로 반복시키기 위해 ForEach를 실행하는 코드를 아래처럼 작성했는데요

ForEach(array, id: \.self) name in

그럴때마다 항상 의미는 모르겠지만 'id: \.self' 를 함께 작성했습니다.

이번 강의에서 폴 교수님이 해당 부분에 대해 자세하게 설명을 해주셨는데요

강의의 예제를 통해 설명해드리겠습니다!

 

일단 ContentView에 카드에 들어가는 이모티콘들을 넣은 emojis 라는 배열을 만들었습니다.

그리고 바로 id를 무시한채 ForEach에 넣고 돌리자 오류가 나버리는데요?!

 

ForEach에 id를 넣지않자 오류가 생김

오류는  'ForEach'에서 이니셜라이저 'init(_:content:)' 참조하려면 'String'이 'Identifiable'준수해야 한다고 말하고 있습니다.

엥 이게 무슨 말이죠? identifiable 한 것은 뭘 의미하는것일까요?

Identifiable는 해석하자면 '식별가능한' 이라는 뜻입니다.

즉 ForEach에 돌릴 Data는 각 요소 별로 식별할 수 있는 id 값이 있어야한다는 뜻인데요?

 

ForEach가 배열의 항목의 id를 원하는 이유는 배열의 각 요소에 대한 뷰를 생성할 것이기 떄문입니다.

배열의 항목이 재정렬, 추가, 삭제 될때마다 배열의 변경사항을 감지하고 그에 따라 View를 조정 해야하는데

( = ForEach가 생성할 View와 일치하도록, Array의 모든 요소를 고유하게 식별 할 수 있어야한다는 뜻)

 

저희가 만든 emojis 라는 배열은 String의 모임이기 때문에 특별히 식별할 수 있는 구분자가 없습니다..

그래서 ForEach에게 “String 자체를 고유 식별자로 사용해라라고 알려줘야하는데요?!

(실제로 고유하지 않더라도 그렇게하라고 해야한다고 하네여)

 

즉 식별자로 사용할 배열의 각 String이 응답하는 이름을 넣어야하는데,

String에는 우리가 알고 있는 고유한 값이 없습니다...

하지만 모든 struct에는 구조체 자체를 의미하는 “self”라는 var를 가지게되는데요

문자열에 .self 라고 쓰면 문체열 자체를 의미하게됩니다.

그래서! id 값으로 self라는 특별한 var 사용하는 것 입니다.

 

ForEach에 id: \.self를 넣자 잘 작도됨!

 

다만 단점은.. ForEach는 같은 문자에 대해 동일한 View를 만들어내게되는 점인데요..

ForEach 같은 문자가 2개 이상일 경우 해당 문자들이 다르다고 구분할 없기 때문에..

같은 문자 입력 이미 동일한 View 다른 같이 사용하게 됩니다..

 

그래서 아래 예시를 보면 같은 문자가 반복되는 비행기 카드 중 하나를 뒤집으면

나머지 비행기 카드 하나도 뒤집히는 것을 확인할 수 있습니다.

(다음 강의에서 해당 부분에 대한 대처법도 다룰 것 같아요~! 만관부!)

'id: \.self'로는 같은 문자열에 대해 같은 View를 만들게 됨

 


 

반복요소 변수로 빼주기

아까 위에서 반복되는 사각형을 지역변수로 빼서 준 것과 같은 맥락으로

반복되는 요소로 인해서 코드가 길어져 가독성을 해치고 그렇다고 따로 Struct로 빼기는 부담스러운 경우!

해당 요소를 변수로 빼서 코드를 정리해줄 수 있습니다.

 

아래 예시를 보면 Hstack안에 카드를 추가하고 삭제하는 버튼이 반복되면서

가독성을 해치고 있는 모습을 확인 할 수 있습니다.

 

body안에 카드 추가 및 삭제 버튼 코드가 반복되고 있다

 

이럴때 body 바깥에 각 버튼을 변수로 빼주고 본문에는 해당 변수 이름만 써주면

훨씬 깔끔하게 코드를 정리 할 수 있습니다.

모두 깔끔하게 가독성 좋은 코드를 작성해보는 연습을 해보세용~

 

 


 

LazyVGrid

세로로 쌓이는 그리드는 LazyVGrid 만들 수 있습니다.

columns에 한 행 당 들어가는 열의 갯수 만큼 Griditem을 입력하면 원하는그리드를 만들 수 있습니다.

LazyVGrid(columns: [GridItem(),GridItem(),GridItem() ∙∙∙ ])

 

만약 행에 들어가는 열을 갯수를 지정하지 않고

디바이스 화면에 꽉 차도록 열의 갯수를 조절하고 싶을땐 .adaptive를 이용해서 줄 수 있습니다.

LazyVGrid(columns: [GridItem(.adaptive(minimum:))])

 

화면의 크기에 따라 들어가는 그리드의 숫자가 달라진다.

 


 

 비율을 조절할땐? aspectRatio

.aspectRatio를 사용하면 View를 원하는 비율로 만들 수 있습니다.

aspectRatio는 aspectRatio, contentMode라는 2개의 파라미터로 비율을 조절 할 수 있습니다.

  • aspectRatio : View의 비율, 현재 비율을 유지하려면 nil 값을 넣으면됨
  • ContentMode : 상위 화면(혹은 프레임)에 맞춰지는지(fit), 채워지는지(fill)를 선택할 수 있음

 

저의 사용 예시를 보시면 더욱 빨리 이해하실 수 있으실거에요!

.aspectRatio(2/3, contentMode: .fit)

= 각 View는 2:3의 비율이고 화면에 가득찰 것이다 라는 뜻

= 만약 LazyVGrid의 columns의 수가 올라가면 2:3 비율을 유지한 채로 가로에 꽉찬 크기로 변경됨

카드 비율 2:3으로 고정

 


 

.strokeBorder

도형의 외곽선을 줄때 .stroke를 사용하면 외곽선 기준 중간부터 선이 생기게됩니다.

이때 도형이 ScrollView안에 들어가 있으면 가장자리의 카드의 선 일부분이 잘리는 현상이 발생하는데

이는 ScrollView는 범위를 벗어나는 영역은 무조건 자르기 때문입니다.

 

stroke로 외곽선을 준 경우

 

이때 선이 잘리고 싶지 않다면 .strokeBorder 를 주면 됩니다!

strokeBorder는 도형의 안쪽을 기준으로 외관선을 만들어 주기 때문에

ScrollView안에 있어도 선이 잘릴 위험이 없기 때문이죠~

 

strokeBorder로 외곽선을 준 경우

 


자 이렇게 오늘 배운 내용을 정리해봤는데요

여러분께도 도움이 되는 정보들이 많았으면 좋겠습니다~

모두 화이팅하시고 즐코딩하세요!

끝까지 읽어주셔서 감사합니다!