[웹개발자의 IOS 탐방기] 5. ListView를 사용한 알림 화면 구현

서론

설정화면 우측 상단에 알림 아이콘을 넣고, 알림 아이콘을 클릭했을 때 알림 리스트들이 나오는 화면을 구현해보려 한다. 또한 각 아이템에 스와이프하여 삭제 기능까지 추가해보자.

SettingView Hedaer 분리

기존 설정 화면에서 구현한 Header부분을 공용으로 사용하기 위해 HeaderView.Swift파일을 생성하여 공용 코드로 관리한다. 우측에 알림 아이콘을 넣어주고, 타이틀을 구분하기 위해 enum HeaderType을 지정해준다. 타입에 맞는 타이틀을 반환하는 함수도 함께 정의한다.

enum HeaderType {
    case setting
    case notice
}
func headerTitle(for type: HeaderType) -> String {
    switch type {
    case .setting: return "설정"
    case .notice: return "알림"
    }
}

이제 HeaderView.Swift파일로 Header 관련 코드를 옮겨 타이틀 및 알림 설정 아이콘을 각 타이틀에 맞춰 변하게 작성해주면 SettingView와 AlarmView에서 같이 사용하는 HeaderView가 만들어진다. 공통된 코드를 여러 View에서 사용하는것은 좋지 않기 때문에 공용으로 사용할 수 있는 View는 이처럼 따로 구현하여 호출하도록 한다.

AlarmView.swift 파일 생성

AlarmView 구조체를 살펴보면, 헤더와 내용 부분으로 나눠져 있는데, 이 구조는 깔끔하게 정리되어 가독성에 좋다고 생각한다. 특히, VStack을 사용해 세로로 뷰 요소들을 배치하는 방식은 SwiftUI에서 자주 사용되는 패턴이니 참고하자.

VStack {
    // Header
    HeaderView(...)
    // End Header
            
    // Content
    List { ... }
    // End Content
}

@Environment(.presentationMode) var presentationMode: Binding를 사용해서 사용자 정의 뒤로 가기 동작을 처리할 준비를 했는데, 이 부분은 SwiftUI의 네비게이션 동작을 조작할 때 매우 유용하게 사용되는 패턴이다.

List와 ForEach를 조합해서 items 배열의 각 항목을 동적으로 표시하는 부분은 SwiftUI의 강력한 기능 중 하나로, 데이터에 따라 UI를 동적으로 업데이트하는 것을 매우 간단하게 할 수 있게 해준다.

List {
    ForEach(items) { item in
        VStack(alignment: .leading) {
            ...
        }
        .padding(.vertical, 5)
    }
    .onDelete(perform: deleteItem)
}

.listStyle(PlainListStyle())와 .background(Color.white)를 사용해 List의 기본 스타일을 수정하는 부분은, SwiftUI의 뷰에 사용자 정의 스타일을 적용하기 위한 좋은 예시이니 참고하자.

// Content
List {
    ForEach(items) { item in
        VStack(alignment: .leading) {
            Text(item.title)
                .font(.headline)
            Text(item.description)
                .font(.subheadline)
            Text(item.updatedText)
                .font(.footnote)
        }
        .padding(.vertical, 5)
    }
    .onDelete(perform: deleteItem)
}
.listStyle(PlainListStyle())  // 배경 색상과 구분선을 제거하기 위한 스타일
.background(Color.white) // 배경 색상 화이트
.ignoresSafeArea(.all, edges: .all)  // 여백 제거하기
// End Content

최종적으로 아래와 같이 AlarmView.swift 코드를 완성하였다.

//
//  AlarmView.swift
//  webApp
//
//  Created by mingyukim on 11/3/23.
//

import SwiftUI

struct AlarmView: View {
    // 사용자 정의 뒤로 가기 동작을 처리하기 위한 변수
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
    @State var items: [NoticeItem] = [
        NoticeItem(title: "알림1", description: "알림1 내용입니다.", updatedText: "2분 전"),
        NoticeItem(title: "알림2", description: "알림2 내용입니다.", updatedText: "10분 전"),
        NoticeItem(title: "알림3", description: "알림3 내용입니다.", updatedText: "1시간 전"),
        NoticeItem(title: "알림4", description: "알림4 내용입니다.", updatedText: "3시간 전"),
        NoticeItem(title: "알림5", description: "알림5 내용입니다.", updatedText: "2일 전"),
    ]
    
    var body: some View {
        VStack {
            // Header
            HeaderView(presentationMode: _presentationMode, isShowingAlarmView: .constant(false),headerType: .notice)
            // End Header
            
            // Content
            List {
                ForEach(items) { item in
                    VStack(alignment: .leading) {
                        Text(item.title)
                            .font(.headline)
                        Text(item.description)
                            .font(.subheadline)
                        Text(item.updatedText)
                            .font(.footnote)
                    }
                    .padding(.vertical, 5)
                }
                .onDelete(perform: deleteItem)
            }
            .listStyle(PlainListStyle())  // 배경 색상과 구분선을 제거하기 위한 스타일
            .background(Color.white)
            .ignoresSafeArea(.all, edges: .all)  // 여백 제거하기
            // End Content
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarHidden(true)
    }
    
    func deleteItem(at offsets: IndexSet) {
        items.remove(atOffsets: offsets)
    }
}

struct NoticeItem: Identifiable {
    var id = UUID()
    var title: String
    var description: String
    var updatedText: String
}

struct AlarmView_Previews: PreviewProvider {
    static var previews: some View {
        AlarmView()
    }
}

결과물

이번에는 SwiftUI의 기본 개념과 패턴을 잘 활용해서 알람 뷰를 구현했다. 특히, List와 ForEach의 조합, 그리고 @Environment를 사용한 네비게이션 동작의 조작은 SwiftUI에서 자주 사용되는 중요한 개념이니 잘 기억해두자