반응형

Raywenderlich 사이트의 강좌 번역본입니다.

 

[원문 : https://www.raywenderlich.com/7589178-how-to-create-a-neumorphic-design-with-swiftui]

 

How to Create a Neumorphic Design With SwiftUI

이 뉴모피즘(neumorphic: 튀어나온듯한 디자인) 디자인 튜토리얼에서, 여러분은 SwiftUI의 강력한 수정자(modifiers)를 사용해서 아름다운 사용자정의 요소를 만드는 방법을 배우게 될 것입니다.

 

다운로드

 

거부하지 마세요. 여러분은 갈망과 두근거리는 마음이 없는 것을 느꼈습니다. iOS 7이 출시된 이후에 거의 터질것 같았습니다. 여러분은 소중한 스큐어모피즘(skeuomorphism: 사물을 그대로 묘사)을 그리워합니다.

 

요즘에 화면을 볼때, 너무나 평평하고 지루해보입니다. 그리고나서 이전의 iOS 7 디자인을 보면, 오래되고 구식입니다. 만약 새롭고 신선한 것처럼 보이는 스큐어모피즘(skeuomorphic) 디자인 언어가 있다면 어떨까요?

 

여러분은 제대로 찾아왔습니다!

 

2019년 말에, 트윗에서 Alexandar Plyuto의 디자인이 돌아다녔습니다. 그리고 그것은 훌륭합니다:

이 튜토리얼에서, 여러분은 SwiftUI로 이러한 디자인 요소들 중 일부를 다시 만드는 방법을 배우게 될 것입니다. 여러분은 다음과 같은 방법을 알게 될 것입니다.

  • 뷰에 깊이를 주기 위해 선형 그라디언트를 사용하기.
  • 그림자와 같은 간단한 것을 결합해서 복잡한 효과 만들기 
  • SwiftUI에서 기본적으로 존재하지 않는 효과인, 역으로 마스크(mask) 하는 기술을 배우기

디자인의 즐거움을 만끽할 준비를 하세요!

시작하기(Getting Started)

스마트 홈(Smart homes)이 요즘에 유행입니다. 뒤쳐지지 않기 위해, 슈퍼 악당과 미친 과학자들이 이제 아주 똑똑해졌습니다. 이봐요, 여러분은 미치지 않고서야 활화산의 분화구에 HQ(본부)를 세우지 마세요.

 

잘 알려지지 않은 비밀은 다음과 같습니다: 앱 슈퍼 악당은 집을 제어하기 위해 SmartLair를 사용하며, 여러분 아이폰의 Home 앱과 같이 평평하고 지루합니다. 아마 더 그럴것입니다.

 

이번 실습에서는, 불법적인 뒷 채널을 이용해서 SmartLair의 소스코드 복사본을 검색했습니다. 여러분은 협박을 받았고, 망가뜨리려고 고용되었습니다.

 

시작하기 위해 이 튜토리얼의 상단과 하단에 다운로드 버튼을 클릭하세요. 시작 프로젝트를 열고 내용을 살펴보세요.

 

기억하세요: 이는 여러분에게 큰 기회입니다. 악당들이 여러분의 일이 마음에 들면 앞으로 더 많은 계약이 이뤄질것입니다. 하지만 그렇지 않으면, 상어밥이 될 것입니다.

선형 그라디언트 소개하기(Introducing Linear Gradient)

시작하기 전에, 여러분은 LinearGradient에 대해 익숙해야 합니다. 스큐어모피즘(skeuomorphism) 디자인은 선형 그라디언트에 많이 의존하고 있씁니다. 그게 큰 문제입니다.

 

SwiftUI에서, 다음과 같이 선형 그라디언트를 정의하세요.

LinearGradient(
  gradient: Gradient(colors: [.white, .lairLightGray]),
  startPoint: UnitPoint(x: 0.2, y: 0.2),
  endPoint: .bottomTrailing
)

 

전체화면을 처리하는 뷰를 사용하면 다음과 같습니다.

여기에서 그라디언트가 나중에 프로젝트에 추가할 Color를 white에서 lairLightGray로 정의합니다. 그라디언트가 여러가지 색상을 통과하는 경우에 2가지 이상의 색상을 가질수 있습니다.

startPoint와 endPoint는 단위 사각형의 상대적인 좌표이며, 좌측상단에서 (0, 0)좌표이고, 우측 하단에서 (1, 1)좌표입니다. 하지만, 이 범위 안에 있을필요는 없습니다. 예를들어, 음의 좌표 값은 뷰의 외부에서 그라데이션을 시작합니다.

 

위의 코드에서, 또한 일반적인 시작과 끝 점에 대해 .leading, .trailing, .top, .bottom과 이들의 조합처럼, 몇가지 미리 정의된 상수도 있습니다.

첫번째 요소 사용자정의하기(Customizing Your First Element)

지루해보이는 AccessoryView를 공략하는 것부터 시작합니다. Control Room Lights Dungeon와 같은 화면 가운데의 큰 사각형을 나타냅니다.

다운로드한 자료에서 Xcode 프로젝트로 Extensions 폴더를 드래그 합니다. Views 그룹 위에 배치합니다. Copy items if needed Create groups가 선택되어 있는지 확인하고나서 Finish를 클릭합니다.

 

이 3개의 파일들을 일부 UIColor 상수를 정의하며, SwiftUI Color 등가와 일부 LinearGradient 정의합니다. 이미 LinearGradient를 만드는 방법을 보았지만, 스큐어모피즘(skeuomorphism) 디자인은 많은 그라디언트를 사용합니다. 하나씩 처리하는데 너무 많은 시간이 걸리고, 슈퍼 악당들이 환자타입이 아니므로 바로 이용할 수 있습니다.

이미지 그라디언트 포함하기(Including the Image Gradient)

AccessoryView.swift에서body에 대한 정의 아래에 VStack에 있는 image로 시작하는 줄을 찾으세요. 해당 줄을 아래 코드로 바꾸지만, frame, padding, font 수정자(modifiers)를 제거하지 마세요.

LinearGradient.lairHorizontalDark
  .mask(image.resizable().scaledToFit())

 

SFSymbol 이미지 그라디언트 마스크로 바꿨습니다. 그라디언트가 있는 레이어는 이미지에서 불투명한 픽셀의 모양으로 잘리게 됩니다. 멋집니다! 변경사항을 확인하기 위해서 빌드하고 실행하거나 Xcode에서 미리보기 캔버스를 켜서 변경사항을 바로 볼 수 있습니다:

강조와 그림자 추가하기(Adding Highlight and Shadow)

font뒤에 이 코드를 추가하세요.

// 1
.shadow(color: .white, radius: 2, x: -3, y: -3)

// 2
.shadow(color: .lairShadowGray, radius: 2, x: 3, y: 3)

 

이 두줄은 다음과 같습니다:

  1. 이미지의 좌측상단을 기준으로 offset된 흰색 그림자를 추가했습니다.
  2. 우측 하단을 기준으로 offset된 어두운 그림자를 추가했습니다.

이 대비(contrast)는 모든 방향에서의 깊이가 있는 것처럼보이도록 제공합니다. 해당 그림자 기술을 사용하면 효과를 높일 수 있습니다. 

모든 수정자를 가진 요소의 코드는 다음과 같이 보여야 합니다.

LinearGradient.lairHorizontalDark
  .mask(image.resizable().scaledToFit())
  .frame(width: 150, height: 236)
  .padding(40)
  .font(.system(size: 150, weight: .thin))
  .shadow(color: .white, radius: 2, x: -3, y: -3)
  .shadow(color: .lairShadowGray, radius: 2, x: 3, y: 3)

 

텍스트 그라디언트 변경하기(Changing the Text Gradient)

텍스트는 분명히 검정색을 유지될수 없습니다. HStack내에 Text에 다음에 오는 수정자를 추가하세요.

.foregroundColor(.lairDarkGray)

 

이제, 여러분의 텍스트는 은은한 회색의 매력적인 그림자입니다. 

완성된 HStack 은 다음과 같아야 합니다.

HStack {
  Text(title)
    .foregroundColor(.lairDarkGray)
    .bold()
    .padding(.leading)
    .padding(.bottom)
  Spacer()
}

 

Text에서 다른 위치에 foregroundColor을 넣어도 여전히 동작합니다. 어떤 방식으로 수정자 순서를 정할수 있지만, 보다시피, 다른것들에게 순서는 중요합니다.

모서리 둥글게하기(Rounding the Corners)

다음 단계는 테두리 모서리를 둥글게 만드는 것입니다. 들리는 것만큼 간단하지는 않습니다. 

 

예를들어, 다음과 같이 해보세요.

.border(Color.gray, width: 1)
.cornerRadius(15)

 

그리고 모서리가 잘려나간 것을 볼 수 있습니다.

다음과 같이 두개의 수정자들을 교환하세요.

.cornerRadius(15)
.border(Color.gray, width: 1)

 

그리고 여러분은 테두리의 날카로운 모서리를 유지하는 것을 알 수 있습니다.

운 좋게도, 해결방법이 있습니다. 오버레이(overlay)를 사용해서 부드러운 곡선 테두리를 얻을수 있습니다. 

 

border를 삭제하고, cornerRadius가 추가된 경우에, 그것들을 다음으로 교체합니다.

.overlay(
  RoundedRectangle(cornerRadius: 15)
    .stroke(LinearGradient.lairDiagonalDarkBorder, lineWidth: 2)
)

 

이 코드는 뷰 위에 다른 뷰(예를들어 오버레이)를 제공하며, 원하는 모서리 반경(corner radius)으로 둥근 사각형을 그려줍니다. 

 

RoundedRectangle 또는 Path 처럼 Shap에 맞는 구조체에 대해 border 대신에 stroke를 사용해서 주변에 선을 그려줍니다. 

 

이 변경으로, Color.gray 대신에 LinearGradient.lairDiagonalDarkBorder을 사용해서 획을 그려 테두리에 그라디언트를 추가합니다. 이 추가는 요소의 좌측상단에 테두리를 강조하고 우측하단에 어두운 그림자를 만듭니다. 동시에, 테두리(border)/획(stroke)의 넓이(width)를 2배로 늘려서 테두리를 두껍게 만듭니다. 

상단과 하단의 테두리가 좌측과 우측보다 더 얇습니다. 이는 뷰가 수직 패딩(vertical padding)이 없고 획(stroke)의 절반만 잘려나가기 때문입니다. 걱정하지 마세요, 조금만 수정하면 됩니다.

테두리 튀어나오게 만들기(Making That Border Pop)

이제, 테두리의 흰색 영역과 강조(highlights)가 눈에 띄길 원합니다. 요소의 배경색상을 변경할 시간입니다.

 

overlay의 닫는 괄호 이후에, 다음에 오는 줄을 추가하세요.

.background(Color.lairBackgroundGray)
.cornerRadius(15)

 

배경은 단순한 색상이 아니라 어떤 View도 될수 있기 때문에, Color.lairBackgroundGray를 전달해야 합니다. 이는 Color만 사용할 수 있는 foregroundColor와는 다릅니다.

테두리 모양 변경하기(Changing the Border Shape)

cornerRadius는 무엇을하지? overlay를 사용해서 만든 테두리는 이미 처리되지 않았나? 스스로 물어볼지도 모릅니다. 

 

일종의 cornerRadius 는 테두리의 모서리 반경을 정의 했습니다. 하지만, 테두리는 뷰 위에 있는 오버레이(overlay)입니다. 이는 cornerRadius가 뷰의 모양을 변경하지 않는다는 것을 의미합니다. 여러분이 직접 기본 도형을 변경해야 합니다. 

 

cornerRadius를 주석처리하는 경우, 뷰가 여전히 모서리 부분이 있고 테두리는 오버레이일뿐입니다.

별로입니다. 하지만 수정자를 다시 주석 해제하면, 모든것이 정상으로 돌아옵니다.

 

AccessoryView가 훨씬 좋아보입니다. 하지만 지금은 튀어나오지 않고, 가운데 기호에 깊이가 부족합니다. 뷰에 깊이를 추가하기 위해서, 기호와 함게 사용한 것과 동일한 기술을 사용하세요: 강조(Highlights)와 그림자(shadows).

 

cornerRadius 바로 아래에, 다음을 추가하세요.

.shadow(
  color: Color(white: 1.0).opacity(0.9),
  radius: 18, 
  x: -18, 
  y: -18)
.shadow(
  color: Color.lairShadowGray.opacity(0.5),
  radius: 14, 
  x: 14, 
  y: 14)

 

첫번째 그림자는 흰색이며, 강조(highlight) 역할을 합니다. 두번째 그림자는 여러분의 그림자입니다.

AccessoryView 문제해결(Troubleshooting AccessoryView)

앱을 실행하는 경우에, AccessoryView를 완전히 볼수 없는 2가지 문제에 직면하게 될 것입니다.

다음과 같습니다:

  1. 배경색상은 흰색으로 유지되므로, 이에 대한 강조(highlight)를 볼수 없습니다.
  2. AccessoryViewRow는 수직 패딩이 없으므로, 그림자(shadow)와 강조(highlight)가 잘립니다.

첫 번째 문제를 해결하기 위해서, LairView.swift를 열고 NavigationView의 VStack을 ZStack에 포함시킵니다. VStack에서 Command-클릭하고 Embed in HStack(ZStacke에 포함 할 수 있는 옵션은 없습니다. 그리고나서 HStack을 ZStack으로 변경하세요.)을 선택합니다.

VStack 바로 위에 새로운 ZStack에 있는 첫번째 요소로 다음에 오는 것을 추가하세요.

Color.lairBackgroundGray.edgesIgnoringSafeArea(.all)

 

원하는 배경색을 Color에 추가하며, 모든 safe area 가장자리를 무시하며 화면을 채울수 있습니다.

 

두 번째 문제를 해결하기 위해서, AccessoryRowView.swift을 여세요. 그리고나서 ScrollView에 있는 전체 HStack에 다음에오는 두 수정자(modifiers)를 추가하세요.

.padding(.top, 32)
.padding(.bottom, 38)

 

이 코드는 뷰의 상단과 하단에 패딩(padding)을 추가합니다. 매우 멋집니다.

 

이제 AccessoryView를 마쳤습니다. 빌드하고 실행하세요.

좋아보이기 시작합니다.

내장된 수정자 소개하기(Introducing Built-in Modifiers)

첫 번째 요소를 사용자정의하기 위해서 한쌍의 View 수정자를 사용했습니다. 하지만 SwiftUI는 내장된 수많은 수정자(modifiers)가 있습니다. 예를 들어:

  • animation : 뷰에 에니메이션을 적용합니다.
  • clipShape : 뷰에 대한 클리핑(clipping) 모양을 설정합니다.
  • onAppear : 뷰가 나타날때 코드를 실행합니다.
  • rotationEffect : 주어진 포인트(point)로 뷰를 회전합니다.

만약 모든 목록에 관심이 있다면, Apple’s documentation을 보세요.

 

여기에 원하는 것을 할수 있는 거의 모든 수정자들이 있습니다. 

마스크 반전 찾기(Discovering Inverse Masks)

탭 바를 다루기 전에, 마스크 반전(inverse masks)에 대해 배워야 합니다.

 

Apple이 mask를 포함했기에, 모든 불투명한 레이어 아래에 있는 hole(구멍)을 잘라낼수 있는 inverseMask가 포함 될것이라 생각할 것입니다.

 

이를 위해서 여러분만의 수정자를 만들어야 합니다. Extensions 그룹에 새로운 Swift File을 추가하세요. 이름은 ViewExtension.swift입니다.

 

그리고나서 다음에 오는 코드로 내용을 교체하세요.

import SwiftUI

extension View {
  // 1
  func inverseMask<Mask>(_ mask: Mask) -> some View where Mask: View {
    // 2
    self.mask(mask
      // 3
      .foregroundColor(.black)
      // 4
      .background(Color.white)
      // 5
      .compositingGroup()
      // 6
      .luminanceToAlpha()
    )
  }
}

 

이는 다음과 같은 것을 할 수 있습니다.

  1. mask를 흉내낸 새로운 inverseMask를 정의합니다.
  2. 입력한 마스크로 마스크되고 수정된 뷰를 반환합니다.
  3. 입력한 마스크의 전경색을 검정색으로 설정합니다.
  4. 입력한 마스크의 전체 배경색은 흰색입니다.
  5. 입력한 마스크를 합성된 그룹으로 감쌌습니다.
  6. 밝기(luminance)를 알파로 변환해서, 검정 전경색을 투명하게 만들고 밝은 배경을 불투명하게 유지합니다. - 마스크 반전(inverse mask)!!

compositingGroup 없이는 동작하지 않는 것을 강조할 가치가 있습니다. 합성(composition) 그룹을 만들기 전에, 배경은 흰색 레이어 이었고, 전경은 투명한 흰색 배경위에 있는 검정 이미지였습니다.

만약 luminaceToAlpha를 호출하면, 검정 배경은 투명하게 되고, 전체 흰색 배경이 표시됩니다.

하지만 compositingGroup를 사용하면, 검정과 흰색 픽셀로 구성된 하나로 그려진 레이어가 됩니다.

luminaceToAlpha를 실행하고나서, 뷰에서 검정 전경색을 잘라냅니다.

휴우! 탭바 버튼에 새로운 효과를 사용할 시간입니다.

탭바 버튼 다루기(Tackling Tab Bar Buttons)

이번 단계는 튜토리얼에서 가장 관련이 있습니다. 약간의 문제는 디자이너가 요구하는 마스크 반전의 사용입니다. 하지만, 이미 그 부분을 해결했습니다.

 

다른 부분은 탭바 버튼을 사용자정의 하기 위한 제한된 옵션입니다. 운이 좋게도, 이전 SmartLair의 개발자는 TableView를 사용하는 방법을 제대로 몰랐기에, 수동으로 구현했습니다. 작업이 쉬워집니다! 이전 개발자는 평화롭게 쉬길 바랍니다.

 

그럼에도 불구하고, 여전히 선택되고 선택되지 않은 버튼 모두를 디자인해야 합니다.

 

시작하기 위해, TabBarItemView.swift를 열고 body의 정의 위에 다음 상수를 추가하세요.

let size: CGFloat = 32

 

네, 하드코딩된 크기입니다. 걱정하지 마세요. 이는 튜토리얼을 위한 것입니다. 나중에 릴리즈하는 시점에 수정할 수 있습니다. :]

 

상수 바로 아래에 도우미 함수 하나를 추가하세요.

func isSelected() -> Bool {
  return selectedItem == smartView
}

 

이 함수는 주석에서 말하는 것을 정확히 수행합니다: 현재 탭 항목이 선택되었는지를 묻습니다. 바인딩된 selectedItem이 정의된 SmartView와 일치하는지를 확인합니다.

 

이는 토글 버튼이기 때문에, 그 함수를 사용해서 탭바 버튼을 표시하는 방법을 결정할 수 있습니다.

 

다음으로, TabBarItemView의 맨 아래에 다음에 오는 것들을 추가하세요.

var buttonUp: some View {
  EmptyView()
}

var buttonDown: some View {
  EmptyView()
}

 

이것들을 사용해서 탭바 항목이 위와 아래에 있을때의 모양을 잘 구성할 수 있습니다. 지금 EmptyView는 Xcode가 사용자가 잔소리하는 것을 막기위한 자리표시자(placeholders)입니다. 

 

이제, body에 있는 Button을 다음과 같이 업데이트 하세요.

Button(action: {
  self.selectedItem = self.smartView
}) {
  // This is the new stuff!
  if isSelected() {
    buttonDown
  } else {
    buttonUp
  }
}

 

사용자에게 표시할 버튼 상태를 결정하기 위해 조건에 따라 Image를 교체하였습니다.

 

남은 것은 버튼 모양을 디자인하는 것입니다.

선택되지 않은 탭바 버튼 디자인하기(Disigning the Unselected Tab Bar Button)

선택되지 않은 버튼으로 시작해서 AccessoryView와 비슷하게 디자인합니다. 하지만, 한가지 차이점이 있습니다. 심볼(symbol)을 표면 위로 올리는 대신에, 표면에서 잘린 것처럼 보이게 만들것입니다. 마스크 반전을 사용할 때입니다!

 

buttonUp을 다음으로 교체하세요.

var buttonUp: some View {
  // 1
  var buttonMask: some View {
    // 2
    ZStack {
      // 3
      Rectangle()
        .foregroundColor(.white)
        .frame(width: size * 2, height: size * 2)
      // 4
      Image(systemName: self.icon)
        .resizable()
        .scaledToFit()
        .frame(width: size, height: size)
    }
  }
    
  // 5
  return buttonMask
}

 

이 코드가 하는 것은 다음과 같습니다.

  1. 버튼에 사용할 마스크를 저장하기 위해 프로퍼티안에 프로퍼티를 정의했습니다. 코드를 좀 더 읽기 쉽게 해줄것입니다.
  2. 마스크의 최상위 뷰로 ZStack를 사용했습니다.
  3. 마스크의 배경으로 동작하도록 흰색 Rectangle를 정의했습니다.
  4. 배경 사각형의 넓이와 높이의 절반인 Image를 만들었습니다. 해당 이미지는 버튼을 마스크 반전으로 바꿀때 잘려나갑니다. 
  5. 마스크를 반환해서, 어떤 모양인지 볼 수 있습니다. 해당 줄이 올바른지 확인하고나서 교체할 것입니다.

캔버스 미리보기 가운데에 매우 간단한 아이콘이 보여야 합니다.

노트: Apple은 이 아이콘을 pencil.tip 이라고 하지만, 여러분의 고객에세 Volcano Lair이라고 알려주세요. 

다음으로, return buttonMask를 다음에 오는 것으로 교체하세요.

// 1
var button: some View {
  // 2
  ZStack {
    // 3
    Rectangle()
      .inverseMask(buttonMask)
      .frame(width: size * 2, height: size * 2)
      .foregroundColor(.lairBackgroundGray)
    }
  }

// 4
return button

 

이는 다음과 같습니다.

  1. 실제 버튼에 대한 다른 프로퍼티를 정의했습니다.
  2. 모든 요소들을 포함하기 위해 ZStack을 사용했습니다. 올것이 더 있습니다.
  3. buttonMask를 마스크 반전으로 사용하는 Rectangle를 만들었습니다.
  4. 버튼을 반환합니다.

캔버스 미리보기를 보면, 마침내 마스크 반전을 만든 노력의 결실을 보게 될것입니다.

선택되지 않은 탭바 버튼 효과 추가하기(Adding Unselected Tab Bar Button Effects)

구멍이 있는 버튼은 멋지지 않으므로, 좀 더 효과를 추가할 것입니다.

 

button의 Rectangle 바로 위에, ZStack에 다음에 오는 LinearGradient를 추가하세요.

LinearGradient.lairHorizontalDarkReverse
  .frame(width: size, height: size)

 

LinearGradinet는 버튼에서 오려낸 심볼을 덮을만큼 커야 하고 오려낸 부분으로 볼 수 있습니다.

다음으로, foregroundColor 바로 뒤에 있는 button의 Rectangel에 다음에 오는 수정자를 추가하세요.

.shadow(color: .lairShadowGray, radius: 3, x: 3, y: 3)
.shadow(color: .white, radius: 3, x: -3, y: -3)
.clipShape(RoundedRectangle(cornerRadius: size * 8 / 16))

 

여기에서, 강조와 그림자를 추가했지만 이전과 반대방향으로 추가 되었습니다. 이는 버튼 중간이 잘려 있는 것에 영향을 주었기 때문입니다. clipshape는 버튼의 모서리를 둥글게 할뿐만 아니라; 해당 범위 내의 강조와 그림자가 포함됩니다. 그것들이 빠져나갔다면, 제대로 보이지 않을 것입니다. 

마지막으로, 전체 ZStack에 이러한 효과들을 추가하세요.

.compositingGroup()
.shadow(
  color: Color.white.opacity(0.9),
  radius: 10, 
  x: -5, 
  y: -5)
.shadow(
  color: Color.lairShadowGray.opacity(0.5),
  radius: 10, 
  x: 5, 
  y: 5)

 

우선, ZStack에 있는 모든 뷰가 합성(composition) 그룹에 있는지 확인하세요. 그리고나서 버튼에 입체감을 주기 위해서 일반적인 강조와 그림자를 추가합니다. 선택되지 않은 버튼은 이제 다음과 같이 보입니다:

그리고 제대로된 배경색을 줄때, 다음과같이 보일 것입니다.

선택 탭바 버튼 디자인하기(Designing the Select Tab Bar Button)

버튼의 선택되지 않은 상태로는 충분하지 않습니다. 마찬가지로 선택된 상태를 추가하는게 필요할 것입니다. 

 

시작하기 전에, TabBarItemView.swift TabBarItemView_Previews 아래쪽으로 스크롤 내립니다. TabBarItemView의 미리보기에 대한 매개변수 목록에서, selectedItem을 .constant(SmartView.lair)이 되도록 변경하세요:

struct TabBarItemView_Previews: PreviewProvider {
  static var previews: some View {
    TabBarItemView(
      selectedItem: .constant(SmartView.lair),
      smartView: .lair, 
      icon: "pencil.tip")
}

 

미리보기 캔버스에서 선택된 버튼이 표시되므로, 변경된 것을 확인 할 수 있습니다.

 

좋습니다. 이제, buttonDown의 현재 구현을 다음에 오는 것으로 교체하세요.

var buttonDown: some View {
  ZStack {
    Rectangle()
      .foregroundColor(.lairBackgroundGray)
      .frame(width: size * 2.25, height: size * 2.25)
      .cornerRadius(size * 8 / 16)
  }
}

 

여기에서, 버튼이 선택될때 모양, 색상, 크기를 정의합니다.

불행하게도, 선택되지 않은 버튼보다 약간 더 큽니다. 다른 각도에서 촬영할때의 단면 효과는 다음과 같습니다.

따라서, 선택된 버튼의 바깥 쪽 테두리를 위해서 약간 더 크게 만들어야 합니다. 이전에, 강조와 그림자에 hidden 되어 있었을 것이지만, 이제는 볼 수 있어야 합니다. 

 

방금 만들었던 아래에 다음에 오는 Rectangle을 추가하세요.

Rectangle()
  .foregroundColor(.lairBackgroundGray)
  .frame(width: size * 2.25, height: size * 2.25)
  .cornerRadius(size * 8 / 16)
  .inverseMask(Rectangle()
    .cornerRadius(size * 6 / 16)
    .padding(size / 8)
  )

 

미리보기는 똑같아 보이지만, 화면을 조정하지 마세요. 대신에, Rectangle의 foregroundColor를 .blue로 변경하세요. 무슨일이 일어나는지 보세요.

보이지 않는 버튼 주변에 테두리를 만들었지만, 이전과 동일한 오버레이(overlay) 기술(trick)을 사용하지 않았습니다. 마스크 반전을 사용해서 버튼의 내부에 그림자를 만들 수 있지만 오버레이(overlay)는 만들수 없기 때문입니다. 

 

.blue를 다시 .lairBackgroundGray로 변경하세요.

 

이제, inverseMask의 닫힌 괄호 뒤에 Rectangle에 이러한 수정자들을 추가하세요.

.shadow(
  color: Color.lairShadowGray.opacity(0.7),
  radius: size * 0.1875,
  x: size * 0.1875, 
  y: size * 0.1875)
.shadow(
  color: Color(white: 1.0).opacity(0.9),
  radius: size * 0.1875,
  x: -size * 0.1875, 
  y: -size * 0.1875)
.clipShape(RoundedRectangle(cornerRadius: size * 8 / 16))

 

이것들은 일반적인 내부 그림자를 추가하고 마스크 반전으로 강조하고 그림자가 다른 쪽으로 번지지 않도록 버튼의 외부 모양을 잘라냅니다.

눌려진 버턴처럼 보이기 시작합니다!

버튼의 심볼 통합하기(Incorporating the Button Symbol)

이제 버튼 심볼을 추가할 것입니다. 버튼이 선택된 보여주기 위해 약간 더 무겁게(즉, 더 어둡게) 할 것입니다. 이번에는 마스크 반전을 건너 뛸 것입니다.

 

마지막 Rectangle와 모든 수정자들 아래에 다음에 오는 것을 추가하세요.

LinearGradient.lairHorizontalDarkReverse
  .frame(width: size, height: size)
  .mask(Image(systemName: self.icon)
    .resizable()
    .scaledToFit()
  )
  .shadow(
    color: Color.lairShadowGray.opacity(0.5),
    radius: size * 0.1875,
    x: size * 0.1875, 
    y: size * 0.1875)
  .shadow(
    color: Color(white: 1.0).opacity(0.9),
    radius: size * 0.1875,
    x: -size * 0.1875, 
    y: -size * 0.1875)

 

해당 뷰에서는 버튼 아이콘을 사용해서 LinearGradient 를 적절한 크기로 마스킹하고나서 강조와 그림자를 추가합니다. 

마지막으로 추가할 효과가 하나 있습니다. 버튼 주변에 멋진 그라디언트 테두리를 주는 것입니다. ZStack의 닫는 괄호 뒤에 다음에 오는 오버레이를 추가하세요:

.overlay(
  RoundedRectangle(cornerRadius: size * 8 / 16)
    .stroke(LinearGradient.lairDiagonalLightBorder, lineWidth: 2)
  )

 

이 오버레이(overlay)는 2포인트 넓이와 대각선 선형 그라디언트는 사용한 둥근 사각형 테두리를 정의합니다.

다음은 적절한 배경 색상으로 보여지는 방법입니다.

세부사항 정리하기(Tidying up the Details)

빌드하고 실행하기 전에, ContentView.swift에서 코드를 약간 변경할 것입니다.

 

우선, 다음에 오는 Rectangle를 찾아서 제거하세요.

Rectangle()
  .frame(height: 1.0 / UIScreen.main.scale)
  .foregroundColor(Color(white: 0.698))

 

탭바와 화면의 남은 부분에 1 픽셀 선이 정의되었지만, 더 이상 필요하지 않습니다.

 

이제, TabBarView의 padding과 backgroundColor을 다음과 같이 변경하세요.

.padding(.bottom, geometry.safeAreaInsets.bottom / 2)
.background(Color.lairBackgroundGray)

 

여기에서 padding의 양을 반으로 줄여서 탭바 버튼을 내리고 나머지 컨텐츠 공간을 제공합니다. 또한 TabBarView의 배경색과 NavigationView과 일치 시켰습니다.

 

빌드하고 실행하면 얼마나 많이 진행했는지 확인하세요.

너무 초라하지 않습니다!

프로그래스바 만들기(Crafting the Progress Bar)

거의 끝나갑니다. 마지막으로 해결해야 할 UI 요소가 하나 있습니다.

 

나쁜 것을 보기 위해서, 프로그래스바가 약간의 그림자와 선형 그라데이션을 이용할 것입니다. 프로그래스 바의 길고 얇은 것을 대체합니다. 

ProgressBarView.swift를 열고 현재 무엇이 있는지 살펴보세요.

 

크게 2개의 섹션으로 나뉩니다: 모든 라벨이 포함된 HStack과 프로그래스바를 그리는 ZStack입니다. ZStack을 보면, 2개의 Capsule로 구성된 것을 볼 수 있습니다. 하나는 전체 바의 길이이고, 다른 하나는 진행에 대한 것입니다. Capsule은 SwiftUI에 포함된 도형입니다. 

 

우선, HStack을 다음과 같이 두개의 Text를 업데이트 하세요.

HStack {
  Text(self.title)
    .foregroundColor(.lairDarkGray)
    .bold()
  Spacer()
  Text("\(Int(self.percent * 100))%")
    .foregroundColor(.lairDarkGray)
    .bold()
}

 

굵은(bold) 텍스트는 바를 더 명확하게 보게하며, 진한 회색 색상은 기본 검은 색상과 약간 거슬리는 대조(constrast)를 제거합니다. 

다음으로 프로그래스바 ZStack 섹션에서, 다른 ZStack에 첫번째 Capsule을 포함시키고, 프레임의 높이를 14로, 전경 색상을 .lairBackgroundGray로 변경하세요.

 

첫번째 캡슐은 이제 다음과 같아야 합니다.

ZStack {
  Capsule()
    .frame(height: 14)
    .foregroundColor(.lairBackgroundGray)
}

 

현재 색상 스키마와 일치하도록 캡슐에 대해 약간 더 밝은 색상을 사용했습니다. 높이가 증가하면 프로그래스 바가 잘려나간 것처럼 보입니다. 

 

같은 Capsule에 있지만, 새로운 ZStack에 LinearGradient를 추가하세요.

LinearGradient.lairHorizontalDarkToLight
  .frame(height: 14)
  .mask(Capsule())
  .opacity(0.7)

 

LinearGradient.lairHorizontalDarkToLight 상단에서 어둡고, 중간에서 100% 선명한 색상으로 변하고 아래쪽에서 흰색으로 끝납니다. 그림자와 강조 효과를 시뮬레이션하는데 사용합니다. 그라디언트 중간의 선명한 색상으로 Capsule의 색상이 빛납니다. 이제 iPhone에서 홈이 잘려나간 것 처럼 보입니다. 

 

또한 이전 Capsule과 일치하도록 프레임의 높이를 설정하고 Capsule이 동일한 모양이 되도록 마스크(mask)를 사용했습니다. 마지막으로, 불투명도는 더 많은 ₩lairBackgroundGray` 색상을 통과시킵니다. 

두번째 캡슐 수정하기(Modifying the Second Capsule)

다음으로, 실제 프로그래스를 그리는 Capsule을 확인하세요. 지금은 지루한 파란색 얼룩 입니다. Capsule와 2개의 수정자(modifiers)를 다음으로 교체하세요.

// 1
ZStack {
  // 2
  LinearGradient.lairHorizontalLight
    // 3
    .frame(
      width: (geometry.size.width - 32) * CGFloat(self.percent),
      height: 10)
    // 4
    .mask(
      Capsule()
        .padding(.horizontal, 2)
    )
}

 

  1. LinearGradient와 다음에 추가할 두번째를 포함하기 위해 ZStack 을 만들었습니다.
  2. 수평 그라데이션을 추가했으며, 라이트(light)에서 다크(dark)까지 프로그래스 바 길이의 중요성을 보여줄 것입니다.
  3. 삭제된 Capsule과 동일한 크기가 사용됩니다. 얼마나 넓을지 계산하기 위해서 percent 프로퍼티와 뷰의 크기를 사용합니다.
  4. 기본적으로 LinearGradient는 사각형이므로 Capsule 로 그라디언트를 마스킹(masked)했습니다. 또한 위치한 곳과 파인곳(groove) 간의 간격을 주기 위해 Capsule에 패딩(padded)을 줍니다.

이전 것 바로 아래에 다음 LinearGradient를 바로 아래에 추가하지만, 여전히 Zstack에 추가하세요.

LinearGradient.lairVerticalLightToDark
  .frame(
    width: (geometry.size.width - 32) * CGFloat(self.percent),
    height: 10)
  .mask(
    Capsule()
      .padding(.horizontal, 2)
  )
  .opacity(0.7)

 

이 그라디언트는 매우 비슷합니다. 하나 다른것은 그라디언트가 수직이고 불투명도가 0.7인 것입니다. 이 그라디언트는 그림자와 강조를 시뮬레이션하지만 파여있는(groove) 그라디언트와 반대방향으로 시뮬레이션 합니다. 프로그래스바 내부가 파여있는 것처럼 보여집니다.

프로그래스바 심화(Deepening the Progress Bar)

프로그래스바에 깊이(depth)를 주기 위해서, 파여있는 부분의 전체 도형에 그림자를 포함시키고자 합니다. 2개의 LinearGradient를 포함하는 ZStack에 다음에 오는 그림자를 추가하세요.

.shadow(
  color: Color.lairShadowGray.opacity(0.5),
  radius: 2, 
  x: 0, 
  y: 1)

 

이 그림자는 파여있는 곳 바깥에서도 보여지기 때문에 최상위 ZStack을 잘라내야 합니다 (괄호를 일치시키는데 도움이 되도록 앞쪽으로 정렬합니다)

.clipShape(Capsule())

 

프로그래스바가 화면에 표시되는 방법은 다음과 같습니다.

이것은 복잡한 섹션이므로, 예상했던 결과가 보이지 않으면, 포함된 최종 프로젝트를 확안해보세요.

네비게이션바 이동하기(Navigating the Navigation Bar)

네비게이션 뷰의 2가지 부분은 새로 스타일이 지정된 앱과 어울리지 않습니다: 네비게이션 바 제목과 프로파일 아이콘.

 

네비게이션 바 제목에 대해서, 모양 프록시(apperance proxy)를 사용해야 합니다. LairView.swift를 열고 LairView에 init를 추가하세요.

init() {
  UINavigationBar.appearance().largeTitleTextAttributes =
    [.foregroundColor: UIColor.lairDarkGray]
}

 

다음은 profileView입니다. 여러분이 도전해볼 것입니다! LinearGradient.lairHorizontalDark를 사용하도록 변경하세요. 이 튜토리얼이 완료하고나면, 스스로 작업하기 위한 모든 것을 알게 됩니다.

 

힌트: 크기를 22 x 22 포인트로 하드코딩해야 합니다.

var profileView: some View {
  LinearGradient.lairHorizontalDark
    .frame(width: 22, height: 22)
    .mask(
      Image(systemName: "person.crop.circle")
        .resizable()
        .scaledToFit()
    )
  .padding()
}

 

다 끝냈을때, 완성된 앱은 경이로워야 합니다.

여기에서 어디로 가야하나요?(Where to Go From Here?)

와우! 이 튜토리얼이 끝났습니다. 모든 악당들을 위한 iOS 수석 엔지니어로써 여러분의 미래는 밝을 것이 분명합니다!

 

튜토리얼의 상단과 하단에서 다운로드 버튼을 사용해서 프로젝트의 완성된 버젼을 다운로드 할 수 있씁니다.

 

현대적인(modern) 스큐어모피즘(skeuomorphism) 스타일로 몇가지 요소들을 만들었지만, 몇가지 시도해야할 다른 것들이 있습니다. 예를들어, 스위치(switch), 슬라이더(slider) 또는 검색 바(search bar)를 다룰 수 있습니다. 스큐어모피즘(skeuomorphism) 디자인을 햅틱(haptics)으로 다음 단계로 올릴수 있습니다. 그것은 정말 특별할 것입니다!

 

SwiftUI에 대해서 더 깊이 알고 싶다면 사이트에서 제공되는 SwiftUI로 스플래쉬 화면 만드는 방법, SwiftUI 애니메이션 시작하기 또는 SwiftUI 튜토리얼 책을 참조하세요.

반응형
Posted by 까칠코더
,