• 테이블은 데이터를 섹션이나 그룹으로 나눌 수 있는 행의 스크롤, 단일 열 목록으로 표시
  • 일반적으로 테이블은 텍스트 기반 콘텐츠에 이상적이다
  • 테이블 뷰는 일반적으로 3가지 스타일로 제공 - Plain, Group, inset Group

Plain

  • 행은 라벨이 지정된 섹션으로 분리될 수 있으며, 인덱스는 테이블의 오른쪽 가장자리를 따라 수직 표시
  • 섹션의 첫 번째 항목 앞에 헤더가 나타날 수 있으며, 마지막 항목 뒤에 푸터가 나타남

Group

  • 행은 그룹으로 표시
  • 헤더 앞에 푸터가 뒤따를 수 있음
  • 항상 적어도 하나의 그룹을 포함하며 각 그룹은 항상 적어도 하나의 행을 포함
  • 그룹화된 테이블에는 인덱스가 포함되어 있지 않음

Inset Group

  • 행은 모서리가 둥글고 부모 뷰의 가장자리에 삽입된 그룹으로 표시
  • 항상 적어도 하나의 그룹을 포함하며 각 그룹은 항상 적어도 하나의 행을 포함
  • 헤더 앞에 푸터가 뒤따를 수 있음
  • Inset Group 된 테이블에는 인덱스가 포함되어 있지 않음
  • 컴팩트한 환경에서 공간이 적기 때문에, 큰 화면의 ( iPad 같은 ) 환경에 적합

고려사항

  • 테이블의 너비를 생각할 것
    • 테이블의 너비가 너무 얇거나 너무 두꺼우면 콘텐츠를 읽는데 오히려 방해 요소가 된다
  • 테이블 내용을 빠르게 표시할 것
    • 이미지나 복잡한 데이터를 포함해야 한다면 텍스트를 먼저 보여주고, 이후에 데이터를 표시
    • 유저에게 즉시 유용한 정보를 제공하고 앱의 반응성을 높일 수 있음
  • 콘텐츠가 로드될 때 진행 상황을 전달할 것
    • 테이블의 데이터를 로드하는 데 시간이 걸린다면, 진행률 표시줄이나 progress 등을 이용하여 앱이 실행 중이라는 것을 사람들에게 인식 시켜야 한다
  • 콘텐츠를 최신화 할것
    • 새로운 데이터를 반영하기 위해 테이블의 콘텐츠를 정기적으로 업데이트하는 것을 고려
    • 스크롤 위치를 변경하지 마세요. ( 수직 스크롤 고정 ) 내용을 테이블의 시작이나 끝에 추가하고, 사람들이 준비가 되면 스크롤하도록 하세요.
    • 일부 앱은 새 데이터가 추가되었을 때 표시기를 표시하고 바로 점프할 수 있는 컨트롤을 제공해야 함.
    • 유저들이 언제든지 수동으로 업데이트를 수행할 수 있도록 새로 고침 컨트롤을 포함하는 것을 권장
    •  참고 문서
  • 인덱스를 오른쪽 정렬된 요소가 포함된 테이블 행과 결합하지 마세요.
    • 테이블 행의 오른쪽에 다른 요소가 있다면 예상하지 못한 제스쳐가 발생하여 유저에게 혼란을 줄 수 있습니다.
    •  참고 문서

Table Rows

표준 테이블 셀 스타일을 사용하여 테이블 행에 콘텐츠가 어떻게 나타나는지 정의

Basic (Default)

행의 왼쪽에 이미지와 왼쪽 정렬된 텍스트가 표시

추가 정보가 필요하지 않은 항목을 표시하는 데 좋음

참고 문서

Subtitle

첫번째 줄에는 왼쪽 정렬된 제목을, 다음 줄에는 왼쪽 정렬된 Subtitle을 정의

행의 구분이 잘 되지않는 항목의 경우 subtitle은 행을 구분하는 데 도움이 된다.

참고 문서

Right Detail (Value 1)

행의 왼쪽에는 Title을, 오른쪽에는 subTitle을 배치

참고 문서

Left Detail (Value 2).

행의 오른쪽에는 Title을, 왼쪽에는 subTitle을 배치

참고 문서

고려 사항

  • 클리핑을 피하기 위해 텍스트를 간결하게 유지할 것
    • 단어나 문장이 잘리면 유저에게 가독성을 낮출수 있다
  • 삭제 버튼에 커스텀 제목을 사용하는 것을 고려할 것
    • 행이 삭제를 지원하고 명확성을 제공하는 데 도움이 된다면, 시스템에서 제공하는 삭제 제목을 커스텀하여 사용하세요
  • 선택 시 피드백을 제공할 것
    • 사람들은 콘텐츠를 탭할 때 행이 짧게 강조되기를 기대한다.
    • 사람들은 새로운 뷰가 나타나거나 체크 표시가 나타나는 것과 같은 무언가가 바뀔 것으로 예상한다
  • 비표준 테이블 행을 위한 커스텀 테이블 셀 스타일을 디자인할 것
    • 표준 스타일은 다양한 일반적인 시나리오에서 사용하기에 적합하지만, 일부 콘텐츠나 전반적인 앱 디자인은 크게 맞춤화된 테이블 모양을 필요로 할 수 있다.
    • 자신만의 셀을 만드는 방법을 배우려면, 참고 문서 를 참고하세요

 참고 문서

State

: 값이 변하면 View를 새로 그려주는 역할을 수행

State 란 무엇인가?

1. State Property store values that the view depends on

⇒ 뷰가 의존하는 상태 속성 저장값

2. State Property represent values that can change

⇒ State Property는 변경할 수 있는 값을 나타냅니다.

3. State Property typically declared in the view that nedds them

⇒ State Property는 일반적으로 이를 추가하는 뷰에서 선언됩니다.

예제 코드

  • State 속성을 사용한 toggleValue 의 변화에 따라 Text가 변하는걸 볼수 있다

Binding

: State의 값을 받아오거나 변경할 때 사용 ( View간 양방향 소통 )

$ 기호를 사용하여 속성을 정의한다

Binding이란 무엇인가?

1. Put a ‘$’ infront of a State Property reference in your view code (Not in the declaration) to turn it into a two way, Read/Write relationship

⇒ view 코드(선언 아님)에서 State Property 참조 앞에 '$'를 넣어 양방향 읽기/쓰기 관계로 전환합니다.

2. @Binding is a property wrapper to indicate that it expects a binding to be passed to the view when an instance of the view is created

⇒ @Binding은 뷰의 인스턴스가 생성될 때 뷰에 바인딩이 전달될 것으로 예상함을 나타내는 속성 래퍼입니다.

예제 코드

  • State를 선언한 View
struct ParentView: View {
    @State private var toggleValue = true
    var body: some View {
        SubView(toggleValue: $toggleValue)
    }
}
  • Binding으로 State변수를 받는 SubView
struct SubView: View {
    @Binding var toggleValue: Bool
    
    var body: some View {
        if toggleValue == false {
            Text("Toggle is False")
        } else {
            Text("Toggle is True")
        }
        
        Button(action: {
            self.toggleValue.toggle()
        }) {
            Text("토글 버튼")
        }
    }
}
  • @Binding 을 명시함 으로써, State변수를 받아옴을 명시
  • 부모 View 에서 Binding으로 값을 넘겨 줄때는 $ 기호를 사용

Observable Object

: 여러 View에서 의존하기를 원할때 사용하는 Object

Observable Object를 사용하기 위한 절차

  1. Class가 Observable Object 프로토콜을 채택해야함
  2. 프로토콜을 채택함으로써 변경하상에 대해 Class의 객체를 관찰하거나 수신
  3. @Published를 선엄하여 변경사항을 저장할 속성을 명확하게 명시 ( Class 내의 개체에 명시 )
  4. @Observed Object 속성을 사용하여 관찰가능한 개체에 대한 참조를 저장

예제 코드

  • 여러 View에서 의존 할 Class 생성
class Profile: ObservableObject {
    @Published var isLogined = false
}

⇒ Class에 Observable Object를 채택

⇒ @Published 를 사용함 으로써 변하는 값을 감시할 변수 명시

struct LoginView: View {
    @ObservedObject var profile = Profile()
    
    var body: some View {
        if profile.isLogined {
        Text("로그인 완료")
        } else {
            Text("버튼을 눌러 로그인 해주세요")
        }
        
        Button(action: {
            profile.isLogined.toggle()
        }){
            Text("버튼")
        }
    }
}

⇒ @ObservedObject 를 사용함 으로써, 변경 사항을 감시할 개체 인스턴스 생성

하지만 이러한 방식을 사용하면 하위 View 모두에게 동일 인스턴스를 전달 할 수 없다 다음 예를 보자

버튼을 클릭하여 로그인을 하면, 로그인 사용자의 닉네임을 보여줄 것이다.

  • LoginView
struct LoginView: View {
    @ObservedObject var profile = Profile()
    
    var body: some View {
        if profile.isLogined {
        Text("로그인 완료")
        } else {
            Text("버튼을 눌러 로그인 해주세요")
        }
        LoginCheckView()
        Button(action: {
            profile.isLogined.toggle()
        }){
            Text("버튼")
        }
    }
}
  • LoginCheckView
struct LoginCheckView: View {
    @ObservedObject var profile = Profile()
    
    var body: some View {
        if profile.isLogined {
            Text("로그인한 사용자 : Chikong.")
        } else {
            Text("로그인 정보가 없습니다.")
        }
    }
}

코드를 작성하고 앱을 실행하면

  • 하위 View의 값이 변하지 않는것을 확인 할 수 있다

이러한 현상이 나타나는 이유는 다음 그림을 살펴보자

  • 다음과 같이 각 View에 새로운 Class 인스턴스를 생성 했기 때문에 각각 다른 인스턴스를 참조 하고 있다.
  • 그렇기 때문에 두개의 인스턴스는 다른 것으로 인식하게 되고, 연관된 값을 변경 하거나 감시할 수 없다

EnvironmentObject

: 하위 View 모두에게 동일한 객체 인스턴스를 사용

위의 예시를 해결할 수 있는 방법이 EnvironmentObject 이다. EnvironmentObject를 사용하면 동일 인스턴스를 하위 View 모두에게 전달 할 수 있다.

위의 문제를 해결할 예제 코드를 보자

  • MainView
import SwiftUI

@main
struct DataFlowApp: App {
    var body: some Scene {
        WindowGroup {
            LoginView()
                .environmentObject(Profile())
        }
    }
}

⇒ 제일 상위 Main View에서 .environmentObject(Profile()) 를 이용해 객체 인스턴스를 넘겨주었다. 이제 MainView의 하위 View에서 동일한 인스턴스를 참조할 수 있다.

  • LoginView
struct LoginView: View {
    @EnvironmentObject var profile: Profile
    
    var body: some View {
        if profile.isLogined {
        Text("로그인 완료")
        } else {
            Text("버튼을 눌러 로그인 해주세요")
        }
        LoginCheckView()
        Button(action: {
            profile.isLogined.toggle()
        }){
            Text("버튼")
        }
    }
}
  • LoginCheckView
struct LoginCheckView: View {
    @EnvironmentObject var profile: Profile

    var body: some View {
        if profile.isLogined {
            Text("로그인한 사용자 : Chikong.")
        } else {
            Text("로그인 정보가 없습니다.")
        }
    }
}

⇒ LoginView 와 LoginCheckView에 @EnvironmentObject var profile: Profile 부분을 보자.

@EnvironmentObject 속성을 명시 했고 같은 인스턴스를 참조함을 구체적으로 적어 주었다

 

⇒ 같은 인스턴스를 참조하기 때문에 로그인상태에 따라 값이 변함을 확인 할 수 있다.

이처럼 하위 View에 동일 인스턴스를 참조해야 할때, 필요한 View에 명시해주면 사용할 수 있다.

 

 

참고 유튜브 : https://www.youtube.com/watch?v=jD6c9y8CFGQ

Nano Challenge 를 진행하는데 횡 스크롤을 구현해야 해서 ScrollView를 공부해 보았다

공식문서 내용

스크롤 뷰는 스크롤 가능한 콘텐츠 영역 내에서 콘텐츠를 표시합니다. 사용자가 플랫폼에 적합한 스크롤 제스처를 수행할 때, 스크롤 보기는 기본 콘텐츠의 어떤 부분이 보이는지 조정합니다. ScrollView는 수평, 수직 또는 둘 다 스크롤할 수 있지만 줌 기능을 제공하지는 않습니다.

  • 어..? HIG에는 적절한 줌 기능을 제공하라고 했는데..? 그건 페이지 컨트롤을 사용할때 한정인가보다.
  • 아직까지는 줌 기능이 필요하지 않으니 추후에 스크롤과 줌 기능이 필요하면 그때 다시 찾아보자.
  • 일단 기본적으로 ScrollView는 줌 기능을 제공하지 않음!

정의

struct ScrollView<Content> where Content : View

→ 정의를 보면 View를 반환하는 구조체로 되어있다.

이 말은 우리는 그냥 ScrollView{ ... return view } 의 형태로 구현하면 된다!

ScrollView 사용해보기

ScrollView() {
	VStack {
		ForEach (0..<20) { i in
			Text("\\(i)")
		}
	}
}

→ 결과

  • ScrollView의 기본 Default 값은 vertical 이라는것을 알수 있다.
  • 스크롤 뷰 내의 구성 요소들을 수직으로 정렬하기 위하여 VStack 에 요소를 넣어주고 구성하면 된다!
  • 내가 필요한 것은 횡 스크롤 이므로 구성 요소를 HStack으로 구성하면 되려나?
ScrollView() {
	HStack {
		ForEach (0..<20) { i in
			Text("\\(i)")
		}
	}
}

→ 결과

응..? 뭔가 이상하다 스크롤도 안될 뿐더러 요소들이 아주 이상하게 배치되어 있다

  • 횡 스크롤을 사용하려면 ScrollView 에 파라미터로 .horizontal 을 설정하자!
ScrollView(.horizontal) {
	HStack {
		ForEach (0..<20) { i in
			Text("\\(i)")
		}
	}
}

→ 결과

 

  • 원하는 대로 동작이 나왔다

아!! 스크롤뷰는

  • Default 값으로 수직 스크롤이 되는구나
  • 횡 스크롤을 구성하려면 horizontal 을 넣어줘야 하는구나!
  • 스크롤내의 요소들을 스크롤 방향에 따라 구성해야 하는구나!

라는 점을 배웠다

그런데 ScrollView의 컴포넌트 중에 showsIndicators 라는 것이 있다. 이게 뭔가 찾아볼까 하다가 직접 해보며 익히는게 좋을거같아 차이를 확인해 봤다

showsIndicators = true

ScrollView(.horizontal, showsIndicators = true) {
	HStack {
	ForEach (0..<20) { i in
		Text("\\(i)")
		}
	}
}

→ 결과

 

⇒ 변한게 없다..? 그럼 false 도.. 확인해보자

showsIndicators = false

ScrollView(.horizontal, showsIndicators = fasle) {
	HStack {
		ForEach (0..<20) { i in
			Text("\\(i)")
		}
	}
}

→ 결과

⇒ 아! 차이가 보인다

HIG에 명시되어 있는 스크롤 표시기가 이 내용인가 보다!

스크롤이 되었을때, 인디케이터가 표시된다 현재 스크롤이 어느지점쯤에 있는지 보여준다!

정리

  • showsIndicators 는 스크롤 진행 표시기? 정도로 이해하면 될거같다
  • 기본 default값으로 true 가 설정된다
  • false를 설정하면 표시기를 없앨 수 있다

ScrollView를 적용한 결과

아. 뿌듯. 이상.

Scroll View 란 무엇인가?

  • Scroll View를 사용하면 문서의 텍스트나 보이는 영역보다 큰 이미지 모음과 같은 콘텐츠를 탐색
  • 사람들이 스와이프하고, 더블탭을 하고, 드래그하고, 탭하고, 줌아웃을 때, Scroll View는 제스처를 따르며, 자연스럽게 느껴지는 방식으로 콘텐츠를 드러내거나 확대
  • Scroll View 자체는 모양이 없지만, 사람들이 상호 작용할 때 일시적인 스크롤 인디케이터를 표시
  • Scroll View는 페이징 모드에서 작동하도록 구성할 수도 있으며, 스크롤은 현재 페이지를 이동하는 대신 완전히 새로운 콘텐츠 페이지 표시

지켜야 할 점

  • 줌 동작을 적절하게 지원할것
    • 앱에서 의미가 있다면, 사람들이 줌아웃을 하거나 두 번 탭하여 확대/축소하도록 하세요.
    • 확대/축소를 활성화할 때, 의미가 있는 최대 및 최소 스케일 값을 설정하세요. 예를 들어, 한 문자가 화면을 채울 때까지 텍스트를 확대하는 것은 대부분의 앱에서 의미가 없습니다.
  • Scroll View가 페이징 모드에 있을 때 페이지 컨트롤 요소를 표시하는 것을 고려할 것
    • 페이지 컨트롤은 사용 가능한 페이지, 화면 또는 기타 콘텐츠 수를 표시하고 현재 볼 수 있는 페이지를 나타냅니다.
    • Scroll View로 페이지 컨트롤을 표시하는 경우, 혼란을 피하기 위해 같은 축의 스크롤 인디케이터를 비활성화하십시오. Page Controls ← HIG 문서 참고
  • Scroll View 안에 또다른 Scroll View를 중첩하지 말것
    • 제어하기 어려운 예측할 수 없는 인터페이스가 생성됩니다.
  • 일반적으로, 한 번에 하나의 Scroll View를 표시할 것
    • 사람들은 종종 스크롤할 때 큰 스와이프 제스처를 하며, 같은 화면에서 중첩된 Scroll View와 상호 작용하는 것을 피하기 어려울 수 있습니다.
    • 한 화면에 두 개의 스크롤 뷰를 넣어야 하는 경우, 한 가지 제스처가 두 View 모두에 영향을 미치지 않도록 다른 방향으로 스크롤하도록 허용하는 것을 고려하십시오.
    • 예를 들어 iPhone이 세로 방향일 때 주식 앱에 회사별 정보 위로 수직으로 스크롤되는 주식 시세 표시가 표시됩니다.

UIScrollView ← 공식문서

Image View 란 무엇인가?

  • Image View는 투명하거나 불투명한 배경 위에 단일 이미지 또는 애니메이션 이미지 시퀀스를 표시
  • Image View 내에서 이미지를 늘리거나, 확장하거나, 크기에 맞게 조정하거나, 특정 위치에 고정될 수 있다.
  • Image View는 기본적으로 상호 작용하지 않는다 ( 각각 고유의 이미지를 타나냄, 다른 이미지에 영향 X )

지켜야 할점

  • 가능하다면, 애니메이션 시퀀스의 모든 이미지가 일관되게 크기인지 확인할것
  • 이미지는 뷰에 맞게 미리 조정되어야 하므로 시스템이 스케일링을 할 필요가 없고, 시스템이 스케일링을 수행해야 하는 경우, 모든 이미지가 크기와 모양이 같을 때 원하는 결과를 얻기 쉽다

UIImageView ← 공식문서 참고

+ Recent posts