IOS(Swift)

[IOS/Swift] ์ฝ˜ํ…์ธ  ๊ฒ€์ƒ‰ ์•ฑ ๊ตฌํ˜„ - 1. ์ฝ˜ํ…์ธ  UI ๊ตฌํ˜„

Tempo 2022. 3. 5. 11:42

ํ˜„์žฌ ์‚ฌ๋‚ด์—์„œ ํ‚ค์›Œ๋“œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜์—ฌ ๊ด€๋ จ ์ฝ˜ํ…์ธ ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์•ฑ์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค.

๊ธฐ์ˆ  ๊ธฐํš - ์•ฑ์— ๋“ค์–ด๊ฐˆ ๊ธฐ์ˆ  ๋ชฉ๋ก

  • ๊ฒ€์ƒ‰ ๋ฐ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ™•์ธ
  • ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ ๊ด€๋ จ ์ฝ˜ํ…์ธ (ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ  ๋˜๋Š” ์ฑ„ํŒ…๋ฐฉ)
  • ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ 
  • ์Œ์„ฑ ์ฑ„ํŒ…๋ฐฉ
  • ๋กœ๊ทธ์ธ

์ฒซ๋ฒˆ์งธ๋กœ ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ ์™€ ๊ด€๋ จ UI๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž.

ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ  UI ๊ตฌํ˜„

๊ฒฐ๊ณผ๋ฌผ ํ™”๋ฉด

ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ  ์ƒ์„ธํ™”๋ฉด ๊ตฌํ˜„

ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ  ์ƒ์„ธํ™”๋ฉด์— ๋“ค์–ด๊ฐˆ ์š”์†Œ๋Š” ์ œ๋ชฉ, ์ถœ์ฒ˜, ์ž‘์„ฑ์ผ, MBTI ํƒ€์ž…, ๊ทธ๋ฆฌ๊ณ  ์ฝ˜ํ…์ธ  ๋‚ด์šฉ์ด๋‹ค.

์ด๋ฅผ ์Šค์œ„ํ”„ํŠธ UI๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

import SwiftUI

struct ContentDetail: View {
    
    var contItem: ContentDetailData
    
    var body: some View {
        VStack(alignment: .leading){
            Group {
								// ์ฝ˜ํ…์ธ ์˜ ํƒ€์ดํ‹€
                Text(contItem.title)
                    .font(.title)
                    .fontWeight(.black)
                    .padding()
                // ์ถœ์ฒ˜์™€ ์ž‘์„ฑ์ผ, MBTI ํƒ€์ž…
                HStack{
                    Text("์ถœ์ฒ˜:")
                    Text(contItem.source)
                }
                .font(.subheadline)
                .padding(.leading)
                HStack{
                    Text("์ž‘์„ฑ์ผ:")
                    Text(contItem.published_at)
                }
                .font(.subheadline)
                .padding(.leading)
                HStack{
                    Text("MBTI:")
                    Text(contItem.mbti.joined(separator: " "))
                }
                .font(.subheadline)
                .padding(.leading)
                
                Divider()
								// ์ฝ˜ํ…์ธ 
                Text(contItem.content)
                    .font(.body)
                    .foregroundColor(.black)
                    .frame(
                        minWidth: 300,
                        maxWidth: .infinity,
                        minHeight: 0,
                        maxHeight: 300,
                        alignment: .topLeading
                    )
                    .padding()
                Divider()
            }
        }
    }
}

struct ContentDetail_Previews: PreviewProvider {
    static var previews: some View {
        ContentDetail(
            contItem: ModelData().contentItems[0]
        ).previewLayout(.fixed(width:310, height:300))
    }
}

์ด UI์—์„œ๋Š” ContentDetailData ๋ฅผ ๋ฐ›์•„์„œ ์ œ๋ชฉ ๋ฐ ์ถœ์ฒ˜ ๋“ฑ์— ์‚ฌ์šฉํ•˜์˜€๋‹ค. ์ด๋Š” ์ถ”ํ›„ REST API ๋กœ ์ฝ˜ํ…์ธ  ์ •๋ณด๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋ฏธ๋ฆฌ REST API์˜ ์‘๋‹ต ํ˜•ํƒœ๋กœ struct ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ContentDetailData์˜ ํ˜•ํƒœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

import Foundation
import SwiftUI

struct ContentDetailData: Hashable, Codable, Identifiable {
    var id: Int
    var title: String
    var content: String
    var source: String
    var published_at: String
    var mbti: Array<String>
    
    private var imageStr: String
    var image: Image {
        Image(imageStr)
    }
}

Python ์—์„œ Dict์™€ ์œ ์‚ฌํ•˜๊ฒŒ struct ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

(*์ด Struct ๋Š” ์ฝ˜ํ…์ธ  ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค„ ๋•Œ๋„ ์‚ฌ์šฉํ•˜๊ธฐ์— ์ œ๋ชฉ, ์ถœ์ฒ˜ ์™ธ์— ๋‹ค๋ฅธ ์นผ๋Ÿผ๋„ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.)

ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ  ๋ชฉ๋ก ๋‚ด Element ํ™”๋ฉด ๊ตฌํ˜„

ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ ๋Š” ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ ๋˜๋Š” ๋ฉ”์ธ ํ™”๋ฉด์—์„œ ์ถ”์ฒœ ์ฝ˜ํ…์ธ ๋กœ ๋ณด์—ฌ์งˆ ์˜ˆ์ •์ด๋‹ค. ๊ทธ๋ž˜์„œ ์ฝ˜ํ…์ธ ์˜ ์ •๋ณด๋ฅผ ๊ฐ„๋žตํ•˜๊ฒŒ ๋‹ด๊ธฐ ์œ„ํ•œ ๋ชฉ๋ก๋‚ด Element ์š”์†Œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค.

์ฝ˜ํ…์ธ  ๋ชฉ๋ก ๋‚ด Element

์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

import SwiftUI

struct ContentItem: View {
    var contentData: ContentDetailData
    
    var body: some View {
        VStack(alignment: .leading){
            ZStack(alignment: .leading){
                contentData.image
                    .resizable()
                    .overlay{
                        Rectangle()
                            .frame(width: 100, height: 100)
                            .opacity(0.3)
                    }
                    .frame(width:100, height: 100)
                    .clipShape(Rectangle())
                    .cornerRadius(8)
                    .overlay(alignment: .top){
                        VStack{
                            Text(contentData.mbti.joined(separator: " "))
                                .frame(width: 80, height: 50, alignment: .topLeading)
                                .foregroundColor(.white)
                                .font(.system(size: 20, weight: .bold))
                                .minimumScaleFactor(0.5)
                                .padding(.top, 10)
                            
                        }
                    }
            }
            
            Text(contentData.title)
                .font(.headline)
                .bold()
                .foregroundColor(.black)
                .frame(width:100)
                .lineLimit(1)
                .padding(0)
                
        }.padding()
    }
}

struct ContentItem_Previews: PreviewProvider {
    static var previews: some View {
        ContentItem(
            contentData: ModelData().contentItems[0]
        )
    }
}

์œ„ ์˜ˆ์‹œ์—์„œ ๋ณด๋ฉด ์‚ฌ์ง„ ์œ„์— ํ…์ŠคํŠธ๊ฐ€ ์˜ค๋ฒ„๋žฉ ๋˜์–ด ์žˆ๋‹ค. ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ZStack์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋ฒ„๋žฉ์„ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

ํ…์ŠคํŠธ ๋ชฉ๋ก ๊ตฌํ˜„

ํ˜„์žฌ๊นŒ์ง€ ๋งŒ๋“  ์ฝ˜ํ…์ธ  Element ์™€ ์ƒ์„ธ ํ™”๋ฉด์„ ์—ฐ๊ฒฐํ•˜๋Š” ๋ชฉ๋ก ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

์ฝ˜ํ…์ธ  ๋ชฉ๋ก ๋ฆฌ์ŠคํŠธ

์ด๋ฅผ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

import SwiftUI

struct ContentRow: View {
    var contentList: [ContentDetailData]
    
    var body: some View {
        VStack(alignment: .leading){
            Text("# ์ถ”์ฒœ์ฝ˜ํ…์ธ ")
                .font(.headline)
                .bold()
                .padding(.leading, 20)
                .padding(.bottom, -8)
                .padding(.top, 10)
            
            ScrollView(.horizontal, showsIndicators: false){
                HStack(alignment: .top, spacing: 0){
                    ForEach(contentList) { item in
                        NavigationLink{
                            ContentDetail(contItem: item)
                        } label: {
                            ContentItem(contentData: item)
                        }
                    }
                }
            }
        }
    }
}

struct ContentRow_Previews: PreviewProvider {
    static var previews: some View {
        ContentRow(contentList: ModelData().contentItems)
    }
}

์ถ”์ฒœ ์ฝ˜ํ…์ธ ์—์„œ ๋ณด์—ฌ์ค„ ์ฝ˜ํ…์ธ ์˜ ๋ชฉ๋ก์€ ๋ช‡๊ฐœ๊ฐ€ ๋ ์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ScrollView ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค. ContentDetail ๋˜๋Š” ContentItem์— ๋„˜๊ธฐ๋Š” ๋ฐ์ดํ„ฐ๋Š” ์œ„์—์„œ ์„ค๋ช…ํ–ˆ๋˜ ContentDetailData ๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

 

๊ตฌํ˜„ ์ฒซ๋ฒˆ์งธ๋กœ ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ  ๋ชฉ๋ก๊ณผ ์ด๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ๋ผ๋ฒจ, ๊ทธ๋ฆฌ๊ณ  ์ƒ์„ธ ํŽ˜์ด์ง€ ๊นŒ์ง€ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

 

๋‹ค์Œ์—๋Š” ์ฑ„ํŒ…๋ฐฉ UI ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž.

๋ฐ˜์‘ํ˜•