[SwiftUI] 여러가지 View와 Modifier 사용법

이상현·2024년 4월 3일

Swift

목록 보기
4/10
post-thumbnail

SwiftUI 공식문서의 내용을 토대로 정리한 게시글 입니다.

SwiftUI의 구성 요소


SwiftUI의 UI 를 구성하는데 사용되는 중요한 요소중에는 View와 Modifier가 있다.

  1. 뷰(View)
    버튼, 네비게이션 바, 텍스트, 이미지, 프로그레스 바 등 눈에 보이는 것들을 말한다. 다른 뷰들을 포함하는 컨테이너 뷰 역할도 할 수 있다.
    View 프로토콜을 준수하는 구조체(struct)로 구현되어있다. 이 프로토콜을 준수하려면 body 프로퍼티를 포함해야 한다.
    대표적으로 Text(), Image() 등이 있다.

  2. 수정자 (Modifiers)
    뷰의 모양, 레이아웃, 동작 등을 변경하는데 사용되는 메소드이다.
    View 프로토콜을 준수하는 모든 뷰에 적용될 수 있고, 적용 시 새로운 View 인스턴스를 반환한다.
    대표적으로 .frame() .padding() 등이 있다.

Modifier


View 종류를 설명하기 전에, 모든 View 에 주로 사용하는 Modifier 들을 먼저 정리하고
특정 뷰에서만 주로 사용하는 것은 따로 설명하겠다.

모든 View 에 주로 사용되는 Modifier

  • 크기 조절: .frame(width: [너비], height: [높이])
    뷰의 너비와 높이를 지정한다.

  • 패딩 추가: .padding([.edges], [길이])
    뷰 주변에 공간을 추가한다. 방향을 지정하지 않으면 모든 방향에 패딩이 적용되고 길이를 지정하지 않으면 기본값이 사용된다.

  • 배경색 설정: .background([색상], alignment: [정렬])
    뷰의 배경에 색상을 설정한다. 이미지를 배경으로 할 수도 있다.

  • 요소 색 설정: .foregroundColor([색상])
    뷰의 텍스트, 아이콘, 벡터, 버튼 등 요소들의 색상을 설정한다.

  • 위치 조정: .offset(x: X축 오프셋, y: Y축 오프셋)
    뷰의 위치를 X축과 Y축 방향으로 이동시킨다. 이는 뷰의 실제 위치를 변경하지 않고, 시각적으로만 이동시킨다.

  • 회전 효과: .rotationEffect(.degrees([각도]))
    뷰를 주어진 각도만큼 회전시킨다.

  • 투명도 조정: .opacity([값])
    뷰의 투명도를 설정한다. 값은 0.0(투명) ~ 1.0(불투명) 사이이다.

  • 그림자 추가: .shadow(color: [색상], radius: [반경], x: [X축 오프셋], y: [Y축 오프셋])
    뷰에 그림자를 추가한다.

  • 테두리 추가: .border([Color], width: [너비])
    뷰의 외곽에 테두리를 추가한다.

  • 모양대로 자르기: .clipShape([Shape])
    뷰의 모양을 지정한 Shape으로 잘라낸다. ex) .clipShape(Circle())

  • 탭했을때 액션: .onTapGesture { 액션 }
    뷰를 탭 가능한 인터랙티브 요소로 만들어 사용자와의 상호작용을 가능하게 한다. 탭 할 시 액션 부분에 작성한 코드가 실행된다.

  • 나타났을 때 액션: onAppear { 액션 }
    뷰가 화면에 나타났을 때 실행할 액션을 만들 수 있다.

View 종류


Text

주로 사용하는 Modifier

  • 표준 폰트 설정: .font(종류)
    .title, .headline, .body 등이 있다.
    SwiftUI 가 제공하는 표준 폰트 사이즈이다. 시스템 설정에 따라 자동으로 조정될 수 있어 접근성이 좋다.

  • 커스텀 폰트 설정: .font(.system(size: 24, weight: .bold, design: .rounded))
    커스텀으로 폰트 굵기, 사이즈, 디자인 등을 지정할 때 사용하면 된다.

  • 굵기 변경: .fontWeight(굵기)
    .light, regular, .semibold, .bold 등이 있다.

  • 텍스트 정렬: .multilineTextAlignment(기준)
    .center, .leading , .trailing 이 있다.

  • 텍스트 최소크기 지정: .minimumScaleFactor(비율)
    텍스트가 주어진 공간에 맞게 줄어들수 있도록 한다. 0.25 로 지정시 최소 25% 까지 크기가 축소 될 수 있음을 의미한다. 위젯이나 워치등 작은 화면의 뷰를 만들 때 반응형으로 만들 때 유용하다.\

  • 최대 줄 수 지정: .lineLimit(줄 수)
    텍스트 뷰가 표시할 수 있는 텍스트의 줄 수를 제한할 수 있다. 제한이 넘어가면 잘릴 수 있다.

예시

struct WelcomeMessageView: View {
    var body: some View {
        Text("닉네임과 프로필 사진을 등록해주세요")
            .font(.system(size: 24, weight: .bold)) // 커스텀 폰트 설정
            .foregroundColor(.black) // 글자색 설정
            .multilineTextAlignment(.center) // 텍스트 중앙 정렬
            .frame(width: 300) // 너비 제한
    }
}

Image

이미지를 보여주는 뷰이다.

Image(이름)
프로젝트 파일 내부의 Assets 안에 넣은 사진의 이름을 문자열로 적으면 된다.

Modifier

  • 크기 조절 가능하게 하기: .resizeable()
    이 메소드를 써줘야 frame 으로 크기 조절이 가능해진다.

  • 이미지 비율 유지: .scaledToFit() 또는 .scaledToFill()
    .scaledToFit()은 이미지의 원본 비율을 유지하면서 주어진 공간에 맞추어 이미지를 조절한다. 이미지가 주어진 공간을 모두 채우지 않을 수 있습니다.
    .scaledToFill()도 이미지의 원본 비율을 유지하면서, 이미지가 주어진 공간을 완전히 채우도록 조절합니다. 이미지의 일부가 잘릴 수 있다.

예시

struct ProfileImageView: View {
	let frameSize: CGFloat = 140
    let borderColor: Color = .gray
    let borderWidth: CGFloat = 4
    let imageName: String

	var body: some View {
        ZStack {
            Image(imageName)
                .resizable()
                .scaledToFit()
                .clipShape(Circle()) // 원형으로 이미지를 자름
                .overlay {
                    Circle().stroke(borderColor, lineWidth: borderWidth)
                }
                .frame(width: frameSize, height: frameSize)
    	}
    }
}

//..

ProfileImageView(imageName: "exampleProfile") // 와 같이 사용

Circle

Circle()

  • 색 채우기: .fill(Color.red)

예시

Circle()
    .fill(Color.gray)
    .frame(width: 140, height: 140)
Circle()
    .stroke(.black, lineWidth: 4)
    .frame(width: 100) // 하나만 정해줘도 됨

RoundedRectangle

RoundedRectangle()

모서리를 둥글게 할 수 있는 사각형이다.

파라미터

  • 둥근 정도 설정: RoundedRectangle(cornerRadius: 5)

예시

RoundedRectangle(cornerRadius: 5)
    .fill(Color.gray)
    .frame(width: 300, height: 60)

뷰 쌓기

VStack HStack ZStack

순서대로 수직, 수평, z축 방향으로 subviews 를 쌓는다.

파라미터

  • VStack(spacing: 30)
    내부의 subviews 간의 간격을 조절한다.

  • ZStack(alignment: .bottomLeading)
    내부 subviews의 정렬을 조절한다. 스택 마다 다른 종류가 있고, 기본값은 center 이다.

예시

HStack(spacing: 18) {
	Image("profileImage")
    Text("Ace")
    VStack {
    	// ..
    }
    // ..
}

빈 공간 만들기

Spacer()
사용 가능한 전체 공간을 확장한다.
여러개 사용할 경우 사용 가능한 전체 공간을 1/n 하여 차지한다.

파라미터

  • 최소 크기만큼 띄우기 : Spacer(minLength: 100)
    지정한 크기보다 크면 더 띄우는데, 아무리 좁혀도 지정한 크기 만큼은 떨어져 있도록 한다.

예시

VStack {
	Text("Hi")
    Spacer()
        .frame(height: 40)
    Text("닉네임과 프로필 사진을 등록해주세요")
    Spacer()
}

Button

사용자가 클릭하고 특정 액션을 행동하는 버튼을 만들 수 있다.

Button(action: {
	// 버튼 클릭시 실행할 액션
}) {
	// 버튼 모양을 나타낼 뷰
}

Modifier

  • 비활성화: .disabled(조건)
    조건이 참일 경우 버튼의 액션 기능이 비활성화 된다.

예시

Button(action: {
    isReady.toggle() // 변수 토글
}) {
    ZStack {
        RoundedRectangle(cornerRadius: 5)
            .fill(isReady ? Color.green : Color.gray) // 조건에 따른 색상 변경
            .frame(width: 300, height: 60)
        Text(user.name)
            .foregroundStyle(.black) // 텍스트 색상 지정
            .font(.title2) // 폰트 스타일 지정
    }
}

ProgressView

작업의 진행 상태를 표시하는 데 주로 사용한다.
(다운로드, 로딩, 작업 진행도 등)

예시

ProgressView("로딩 중..")

struct GameProgressBarView: View {
    var currentIndex: Int // 현재 인덱스. 이 값은 상위 뷰 에서 매번 전달받아서 업데이트 된다.
    var totalCount: Int // 총 카운트

    var body: some View {
            ProgressView(value: Float(currentIndex), total: Float(totalCount))
                .progressViewStyle(LinearProgressViewStyle(tint: Color.blue)) // 프로그레스 바의 색상 지정
                .scaleEffect(x: 1, y: 2, anchor: .center) // 프로그레스 바의 높이 조절
                .animation(.linear(duration: 0.5), value: currentIndex)
                .padding() // 적절한 패딩 추가
        }
}

Gauge

값의 범위를 게이지로 표시 하는데 사용된다.
(배터리 레벨, 속도계, 온도계 등)

Gauge(value: speed, in: 0...100) {
    Text("Speed")
}

ScrollView

ScrollView {}
스크롤 뷰는 스크롤 가능한 콘텐츠 영역 내에 콘텐츠를 표시한다.

파라미터

  • 스크롤 방향 설정: .vertical, .horizontal

예시

var body: some View {
    ScrollView {
        VStack(alignment: .leading) {
            ForEach(0..<100) {
                Text("Row \($0)")
            }
        }
   }
}

네비게이션

다른 화면을 불러올 때 사용하는 컨테이너 뷰이다.
루트 뷰 위에 다른 뷰 스택을 쌓는다.

NavigationLink 를 사용하여 스택 상단에 뷰를 쌓을 수 있고, 자동으로 생성되는 뒤로가기나 스와이프 동작으로 뷰를 제거할 수 있다. (루트 뷰는 제거 불가능)

NavigationStack {
	// ..
    NavigationLink(destination: ScrollViewTest()) {
        // 다음 페이지로 이동하는 버튼의 뷰
    }
}

예시

struct ViewTest: View {
    var body: some View {
        NavigationStack {
            VStack {
                Spacer()
                    .frame(height: 40)
                Text("닉네임과 프로필 사진을 등록해주세요")
                    .font(.system(size: 24, weight: .bold))
                    .multilineTextAlignment(.center)
                    .frame(width: 300)
                Spacer()
                    .frame(height: 50)
                    
                NavigationLink(destination: ScrollViewTest()) {
                    ZStack {
                        RoundedRectangle(cornerRadius: 25.0)
                            .frame(width: 100, height: 50)
                        Text("다음")
                            .font(.title3)
                            .foregroundStyle(.black)
                    }
                }
            }
        }
    }
}

딜레이

Tesk.sleep()

예시

Task {
	try await Task.sleep(nanoseconds: 500_000_000)
    //.. 이 뒤에 넣는 코드는 0.5초 뒤에 실행된다.
}
// 블럭 밖은 비동기로 동시에 실행됨

애니메이션

withAnimation

withAnimation(애니메이션) {}

{} 블럭 내부의 값이 바뀌면, 그 값의 영향을 받는 모든 뷰에 지정한 애니메이션을 적용할 수 있다.

애니메이션: 애니메이션 종류 지정 가능.

  • .default: 기본
  • .linear: 같은 속도
  • .easeIn: 점점 빨라지는 속도
  • .easeOut: 점점 느려지며 멈춤
  • .spring: 스프링같은 애니메이션

예시

struct ViewTest: View {
    @State private var isAnimation = true
    
    var body: some View {
        VStack {
            Rectangle()
            	.foregroundColor(isAnimation ? .green : .red)
                .frame(width: 100, height: isAnimation ? 100 : 200)
                .opacity(isAnimation ? 0.3 : 1)
                .offset(x: isAnimation ? 0 : 50)
            Button(isAnimation ? "나타나" : "사라져") {
                withAnimation(.default) {
                    isAnimation.toggle()
                }
            }
        }
    }
}

0개의 댓글