# Color

SwiftUI에서는 컬러 구조체를 제공한다. 시스템 컬러들은 다크모드에 따라 자동으로 최적화된다. 같은 컬러라고 하더라도 각 모드에 최적화된 시스템 컬러를 제공하기 때문에 세부 RGB는 다르게 설정되어 있다.

또한 컬러는 View프로토콜을 채택하기 때문에 뷰처럼 프레임 지정 등의 레이아웃 설정이 가능하다.

struct Color_Tutorials: View {
    var body: some View {
        VStack {
            Color.blue
            Color.gray
        }
    }
}

당연히 컬러를 직접 생성할 수 있다.

Color(red: 70 / 255, green: 80 / 255, blue: 12 / 255, opacity: 0.5)

위와 같이 명시적인 컬러 지정을 한 경우 다크모드 여부에 상관없이 고정된 컬러를 표시한다.

번들의 Assets파일에 다크 Appearance로 생성된 컬러셋을 직접 정의하면 다크모드 여부에 따라 자동으로 컬러 변경이 이루어진다. 코드 단계에서 별도의 분기처리가 필요없어진다. 이러한 컬러를 네임드 컬러라고 한다.

네임드 컬러에 오타가 발생하지 않도록 주의하자.

Color("ColorSet")
// Color(name: String, bundle: Bundle)

# Materials

머터리얼은 불투명도를 나타낸다. 투명한 순서대로 차례대로 .ultraThinMaterial, thinMaterial, regularMaterial, thickMaterial, ultraThickMaterial로 이어진다.

Text("Lorem Ipsum")
    .frame(maxWidth: .infinity)
    .frame(height: 80)
    .background(.ultraThickMaterial)

# Images

이미지에는 labeled imageunlabeled image가 있다. 보이스오버로 접근성 지원을 하는 경우 앱 내에서 중요하게 사용되는 이미지에는 labeled image를 사용하고 그렇지 않은 단순 장식용 이미지에는 unlabeled image를 사용한다. 보이스오버는 레이블 이미지만 인식하여 읽어준다.

labeled image의 경우 Image 생성자 파라미터에 label가 포함된 함수를 호출한다. unlabeled image의 경우에는 decorative파라미터가 붙어있는 함수를 호출한다.

이미지 사이즈를 조정해야 하는 경우 frame 모디파이어를 호출하면 되는데 프레임에 대한 변경만 이루어지고 이미지 자체 사이즈는 변경되지 않는다.

resizable 모디파이어를 호출하면 이미지 종횡비와 상관없이 프레임에 전달한 너비 높이값이 맞추게 된다.

이때 scaledToFill이나 scaledToFit 모디파이어를 호출하면 종횡비에 맞춰 프레임 내에서 이미지 사이즈가 재조정된다.

scaledToFit은 종횡비를 맞추는 과정에서 프레임을 벗어나지 않는 것을 우선순위로 하고, scaledToFill은 프레임을 벗어나도 종횡비를 맞추는 것을 우선순위로 한다.

clipped 모디파이어로 프레임을 벗어나는 컨텐츠를 자르도록 할 수 있다. interpolation 모디파이어는 컨텐츠 품질을 지정할 수 있다.

또한 이미지는 renderingMode모디파이어를 사용할 수 있다. 백그라운드가 없는 이미지에 템플릿 속성을 적용하면 컬러를 직접 적용할 수 있다.

Image("flower")
    .resizable()
    .renderingMode(.template)
    .foregroundStyle(.yellow)
    .frame(width:200, height: 200)

# SFSymbols

심볼 이미지는 폰트처럼 font모디파이어로 사이즈를 지정할 수 있다. SF 심볼 이미지에 imageScale 모디파이어를 적용하면 텍스트와 어울리게 이미지 스케일을 조정할 수 있다.

Image(systemName: "star")
    .font(.largeTitle)
    .imageScale(.large)

심볼 이미지로 스케일을 조정하면 다이나믹 타입에 따라 크기가 자동으로 조정된다는 장점도 있다. 또한 심볼이미지는 벡터 기반의 이미지라 깨짐도 없다.

템플릿 이미지로 사용하는 경우 일반 이미지보다 자유도가 높다. SF 3.0 버전 이후부터 여러 색들을 지원한다.

Image(systemName: "cloud.sun.rain")
    .font(.largeTitle)
    .imageScale(.large)
    .symbolRenderingMode(.palette) // 렌더링 모드 지정
    .foregroundStyle(.gray, .yellow, .blue) // 컬러 각각 지정

# AsyncImage

AsyncImage는 로컬 이미지를 사용하지 않고 서버로부터 이미지를 직접 다운로드 할때 비동기 처리를 자동으로 도와주는 뷰이다. 메인스레드 블록이 없으므로 메인스레드 처리는 불필요하다.

URL 객체를 파라미터에 전달하고, content 클로저에서 Image파라미터에 뷰를 정의하면 된다. 클로저 파라미터가 Image뷰이며 AsyncImage는 이미지 다운로드를 도와주는 도구일 뿐이다.

placeholder 클로저에서 다운로드 과정에 표시할 뷰를 구현하면 된다.

AsyncImage(url: url) { image in
    image.resizable()
        .scaledToFit()
} placeholder: {
    ProgressView()
}

다운로드 실패에 대한 처리를 하고싶은 경우 AsyncImagePhase타입을 파라미터로 받는 content클로저를 구현하면 된다. phase를 switch문으로 분기처리하여 failure케이스에서 다운로드 실패에 대한 뷰를 정의하면 된다.

AsyncImage(url: url) { phase in
    switch phase {
    case .success(let image):
        image.resizable()
            .scaledToFit()
    case .failure(_):
        Label("이미지 다운로드 실패", systemImage: "x.circle")
    case .empty:
        ProgressView()
    @unknown default:
        fatalError()
    }
}