Project10의 네 번째 포스팅입니다.
디테일 화면 구현하기
전 포스팅에서 내비게이션 링크까지 연결을 했습니다. 이제 실제로 상세 화면을 구현할 차례입니다.
[SwiftUI View
] 템플릿을 통해서 ProductDetailView.swift
라고 이름 짓고 다음 코드를 채워 넣어주겠습니다. 우선 아래 코드를 넣어주고 하나하나씩 구현하도록 할게요.
// ProductDetailView.swift
import SwiftUI
struct ProductDetailView: View {
// 정보를 전달받기 위한 프로퍼티 선언
let product: Product
var body: some View {
VStack(spacing: 0) {
productImage //영화의 이미지가 올 부분
orderView // 영화 정보를 출력하고 그 영화를 구매 하기 위한 뷰
}
.edgesIgnoringSafeArea(.top)
}
}
edgesIgnoringSafeArea(.top)
수식어를 넣어서 윗부분까지 채워주도록 했고, 이제 productImage
의 뷰를 표현해보겠습니다.
productImage
영화의 이미지를 나타내는 부분
// ProductDetailView.swift
var body: some View {
VStack {
productImage //영화의 이미지가 올 부분
orderView // 영화 정보를 출력하고 그 영화를 구매 하기 위한 뷰
}
.edgesIgnoringSafeArea(.top)
}
}
private extension ProductDetailView {
var productImage: some View {
Image(self.product.ImageName)
.resizable()
.scaledToFill()
}
}
orderView
orderView
에는 여러 코드가 들어가기 때문에 역할에 따라 여러 개의 뷰로 나누어 주도록 하겠습니다.
// ProductDetailView.swift
private extension ProductDetailView {
var productImage: some View {
Image(self.product.ImageName)
.resizable()
.scaledToFill()
}
}
var orderView: some View {
GeometryReader {
VStack(alignment: .leading) {
self.productDescripsion //영화제목과 즐겨찾기 버튼 이미지 추가
Spacer()
// 아래 코드는 잠시 주석 처리
// self.priceInfo // 가격
// self.orderButton // 주문버튼
}
.frame(height: $0.size.height + 10)
.padding(30)
.background(Color.white)
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.4), radius: 10, x: 0, y: -5)
}
}
ProductDescription
자 이제 productDescripsion
부분을 구현해주겠습니다. 이곳에는 캐릭터명, 즐겨찾기 버튼, 설명 등을 표시하는 뷰가 됩니다.
var productDescripsion: some View {
VStack(alignment: .leading, spacing: 10) {
HStack {
Text(product.name)
.font(.largeTitle)
.foregroundColor(.black)
Spacer()
Image(systemName: "heart") // 즐겨찾기 버튼
.font(.title2)
.foregroundColor(.lightRed)
.frame(width: 32, height: 32)
}
Text(product.description) //상품 설명
.foregroundColor(.gray)
}
}
}
한 가지 함수를 작성해줘야 하는데 한 문장으로 길게 된 텍스트를 화면에 적절하게 나태 나게 위해 두 줄로 나누어 주는 기능을 수행하도록 하겠습니다. 이 코드는 아래 게시글에서 자세하게 확인할 수 있습니다. 이번 포스팅에서는 설명하지 않겠습니다. :)
아래와 같이 함수를 작성해주고 Text
부분을 Text(splitText(product.description))
으로 수정해줍니다.
// ProductDetailView.swift
import SwiftUI
struct ProductDetailView: View { ... }
private extension ProductDetailView {
// MARK : View
var productImage: some View { ...}
var orderView: some View { ... }
var productDescripsion: some View {
VStack(alignment: .leading, spacing: 10) {
HStack {
...
}
Text(splitText(product.description))
.foregroundColor(.gray)
}
}
/* 한 문장으로 길게 구성된 텍스트를 화면에 적장하게 나타내기위해 두 줄로 나누워 주는 기능 */
func splitText(_ text: String) -> String {
guard !text.isEmpty else { return text }
let centerIdx = text.index(text.startIndex, offsetBy: text.count / 2)
let centerSpaceIdx = text[..<centerIdx].lastIndex(of: " ")
?? text[centerIdx...].firstIndex(of: " ")
?? text.index(before: text.endIndex)
let afterSpaceIdx = text.index(after: centerSpaceIdx)
let lhsString = text[..<afterSpaceIdx].trimmingCharacters(in: .whitespaces)
let rhsString = text[afterSpaceIdx...].trimmingCharacters(in: .whitespaces)
return String(lhsString + "\n" + rhsString)
}
}
현재까지의 코드
<hide/>
// ProductDetailView.swift
import SwiftUI
struct ProductDetailView: View {
// 정보를 전달받기 위한 프로퍼티 선언
let product: Product
// MARK : Body
var body: some View {
VStack(spacing: 0) {
productImage // 이미지가 올 부분
orderView // 영화 정보를 출력하고 그 상품을 주문하기 위한 뷰
}
.edgesIgnoringSafeArea(.top)
}
}
private extension ProductDetailView {
// MARK : View
var productImage: some View {
Image(self.product.ImageName)
.resizable()
.scaledToFill()
}
var orderView: some View {
GeometryReader {
VStack(alignment: .leading) {
self.productDescripsion //영화제목과 즐겨찾기 버튼 이미지 추가
Spacer()
// self.priceInfo // 가격
// self.orderButton // 주문버튼
}
//geometryReader가 차지하고있는 뷰 높이보다 VStack의 높이가 10 크도록 지정해줌
.frame(height: $0.size.height + 10)
.padding(30)
.background(Color.white)
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.4), radius: 10, x: 0, y: -4)
}
}
var productDescripsion: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
Text(product.name)
.font(.largeTitle)
.foregroundColor(.black)
Spacer()
Image(systemName: "heart") // 즐겨찾기 버튼
.font(.title2)
.foregroundColor(.lightRed)
.frame(width: 32, height: 32)
}
Text(splitText(product.description))
.foregroundColor(.gray)
}
}
// MARK : Func
/* 한 문장으로 길게 구성된 텍스트를 화면에 적장하게 나타내기위해 두 줄로 나누워 주는 기능 */
func splitText(_ text: String) -> String {
guard !text.isEmpty else { return text }
let centerIdx = text.index(text.startIndex, offsetBy: text.count / 2)
let centerSpaceIdx = text[..<centerIdx].lastIndex(of: " ")
?? text[centerIdx...].firstIndex(of: " ")
?? text.index(before: text.endIndex)
let afterSpaceIdx = text.index(after: centerSpaceIdx)
let lhsString = text[..<afterSpaceIdx].trimmingCharacters(in: .whitespaces)
let rhsString = text[afterSpaceIdx...].trimmingCharacters(in: .whitespaces)
return String(lhsString + "\n" + rhsString)
}
}
// MARK : Preview
struct ProductDetailView_Previews: PreviewProvider {
static var previews: some View {
ProductDetailView(product: ProductList[0])
}
}
PriceInfo
이제 orderView
에서 주석 처리한 priceInfo
부분을 활성화시킨 뒤, 아래와 같이 코드를 작성해주겠습니다. product
의 price
부분은 Int
정수 이기 때문에 Text("product.price")
로 바로 가져오게 되면 컴파일 오류가 생깁니다. 그렇기 때문에 반드시 문자열 보간을 사용하여 호출해야 합니다.
// ProductDetailView.swift
var priceInfo: some View {
HStack {
Text("₩") + Text("\(product.price)").font(.title).fontWeight(.medium)
Spacer()
}
.foregroundColor(.black)
}
orderButton
주문을 위한 버튼을 만들어 보겠습니다. 마찬가지로 orderView
에서 주석 처리한 부분을 활성화시켜줍니다.
// ProductDetailView.swift
var orderButton: some View { // 주문하는 버튼
Button(action: {
// some action
}) {
Capsule()
.fill(Color.lightRed)
// 너비는 주어진 공간을 최대로 사용, 높이는 최소30, 최대 60으로 지정
.frame(maxWidth: .infinity, minHeight: 30, maxHeight: 60)
.overlay(Text("주문하기")
.font(.system(size: 23)).fontWeight(.medium)
.foregroundColor(.white))
.padding(.vertical, 10)
}
}
PriewView / Home View 코드 수정
프리뷰와 Home
뷰 에도 아래와 같이 코드를 추가해줘야 정상적으로 실행이 됩니다.
// ProductDetailView.swift
struct ProductDetailView_Previews: PreviewProvider {
static var previews: some View {
ProductDetailView(product: ProductList[0])
}
}
import SwiftUI
struct Home: View {
let movie: Movie
var body: some View {
NavigationView {
List(movie.products) { product in
NavigationLink(
destination: ProductDetailView(product: product)
//내비게이션 back 버튼 숨김
.navigationBarBackButtonHidden(true)) {
ProductRow(product: product)
}
}
.navigationBarTitle("지브리 스튜디오")
}
}
}
앱을 실행해보면 크기가 제각각 다른 것을 확인할 수 있죠? 이유는 이미지 크기가 전부 동일하지 않기 때문입니다. 이것을 해결하려면 productImage
프로퍼티에서 이미지를 GeometryReader
로 감싸줘야 합니다.
productImage
// ProductDetailView.swift
var productImage: some View {
GeometryReader{ geo in
Image(self.product.ImageName)
.resizable()
.scaledToFill()
// 이미지 중앙을 기준으로 양옆을 자름
.frame(width: geo.size.width, height: geo.size.height)
.clipped()
}
}
이제 모든 뷰가 동일한 크기로 구현되었고, 위 코드에서 추가한 frame
부분을 한번 비교해보도록 하겠습니다.
읽어주셔서 감사합니다🤟
[프로젝트/간단] - SwiftUI Project10 : 영화 캐릭터 정보 앱 #1
'PROJECT > Simple' 카테고리의 다른 글
SwiftUI Project11 : Shape를 활용하여 뷰 꾸미기 (3) | 2021.04.24 |
---|---|
SwiftUI Project10 : 영화 캐릭터 정보 앱 #3 - JSON (0) | 2021.04.19 |
SwiftUI Project10 : 영화 캐릭터 정보 앱 #2 - 모델생성 (0) | 2021.04.17 |
SwiftUI Project10 : 영화 캐릭터 정보 앱 #1 (0) | 2021.04.16 |
SwiftUI Project9 : CustomTabView (geometryReader) (0) | 2021.03.26 |