728x90
0. 이전
- 고정된 일정 수
- 이번에 '+' 아이콘을 사용해 일정을 추가할 수 있는 기능을 만들어보자
1. .toolbar = 상단 버튼 영역
앱 상단에 버튼을 배치하는 영역
캘린더 앱의 '+'버튼 처럼
우리도 상단 오른쪽에 배치할 예정
.toolbar {
ToolbarItem(placement:
.navigationBarTrailing) {
Button {
showingAddEbent = true
} label: {
Image(systemName: "plus")
}
}
- .toolbar는 NavigationStack 안에서만 동작
- placement: .navigationBarTrailing = 오른쪽 상당 배치
- 버튼 탭 -> showingAddEvent = true -> Sheet 열림
2. 입력화면 띄우기 .sheet (모달)
- 화면 아래에서 위로 슬라이드 되며 나타남
- 기존 화면 위에 덮어 씌우는 형태
- isPresented 값이 true가 되면 열림, 닫으면 자동으로 false
@State var showingAddEvent = false
// NavigationStack 안에서
.sheet(isPresented:
$showingAddEvent) {
AddEventView(
events: $events
)
}
.sheet 동작 흐름
- + 버튼
- showingAddEvent = true
- Sheet 올라옴(AddEventView)
- 저장 or 취소 후 화면 닫힘, showingAddEvent = false
3. TextField = 텍스트 입력 칸
- 사용자가 키보드로 텍스트를 입려하는 칸
@State var title = ""
@State var memo = ""
TextField("제목을 입력하세요",
text: $title)
TextField("메모을 입력하세요",
text: $memo)
제목이 비어있으면 저장 버튼 비활성화!
- .disabled(title.isEmpty)
Toggle 사용 (하루 종일 옵션)
- Toggle("하루종일", isOn: $isAllDay)
- 켜면 true, 끄면 false
4. DatePicker
- 캘린더 형태로 날짜/시간을 선택하는 UI
- 탭하면 캘린더가 펼쳐짐
- 날짜와 시간 모두 선택 가능
- @State var date = Date()로 저장
@State var date = Date()
DatePicker("날짜",
selecetion: $date,
displayedComponents:
[.date, .hourAndMinute]
)
// .date = 날짜만
// .hourAndMinute = 시간만
// 둘 다 = 날짜 + 시간
5. 데이터 추가하기 @Binding으로 목록에 추가!
- @Binding이란 부모 데이터를 자식이 수정할 수 있게 해주는 연결고리!

// AddEventView 안에서
@Binding var events: [Event]
func saveEvent() {
let newEvent = Event(
title: title,
date: date,
memo: memo,
isAllDay: isAllDay
)
events.append(newEvent)
dismiss() // sheet 닫기
}
6. 프롬프트 작성
일정관리 앱을 이어서 개발할게.
추가할 기능:
1. 상단에 + 버튼이 있다
2. + 버튼을 탭하면 일정 추가 화면이 나타난다
3. 제목, 날짜, 메모를 입력할 수 있다
4. 하루 종일 토글이 있다
5. 저장 버튼을 누르면 목록에 추가된다
6. 취소 버튼을 누르면 추가하지 않고 닫힌다
7. 제목이 비어있으면 저장 버튼이 비활성화된다
전체 코드를 하나의 파일로 다시 작성해줘.
import SwiftUI
// MARK: - 모델
struct Event: Identifiable {
let id = UUID()
var title: String
var date: Date
var memo: String
var isAllDay: Bool
}
// MARK: - 샘플 데이터
extension Event {
static func 샘플만들기() -> [Event] {
let 날짜형식 = DateFormatter()
날짜형식.dateFormat = "yyyy-MM-dd"
let 시간형식 = DateFormatter()
시간형식.dateFormat = "yyyy-MM-dd HH:mm"
return [
Event(title: "팀 회의", date: 시간형식.date(from: "2026-03-18 14:00")!, memo: "분기 목표 점검", isAllDay: false),
Event(title: "치과 예약", date: 시간형식.date(from: "2026-03-20 10:30")!, memo: "정기 검진", isAllDay: false),
Event(title: "친구 생일 파티", date: 날짜형식.date(from: "2026-03-22")!, memo: "선물 준비하기", isAllDay: true),
Event(title: "프로젝트 마감", date: 시간형식.date(from: "2026-03-25 18:00")!, memo: "최종 보고서 제출", isAllDay: false),
Event(title: "가족 저녁 식사", date: 날짜형식.date(from: "2026-03-30")!, memo: "레스토랑 예약 완료", isAllDay: true),
]
}
}
// MARK: - 날짜 표시 헬퍼
struct DateLabel: View {
let date: Date
let isAllDay: Bool
var body: some View {
if isAllDay {
Text(date, format: .dateTime.year().month().day()) + Text(" (하루 종일)")
} else {
Text(date, format: .dateTime.year().month().day().hour().minute())
}
}
}
// MARK: - 목록 행
struct EventRow: View {
let event: Event
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(event.title)
.font(.headline)
DateLabel(date: event.date, isAllDay: event.isAllDay)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
}
}
// MARK: - 상세 화면
struct EventDetailView: View {
let event: Event
var body: some View {
List {
Section("제목") {
Text(event.title)
.font(.title3.bold())
}
Section("날짜") {
DateLabel(date: event.date, isAllDay: event.isAllDay)
}
Section("메모") {
Text(event.memo.isEmpty ? "없음" : event.memo)
.foregroundColor(event.memo.isEmpty ? .secondary : .primary)
}
}
.navigationTitle("일정 상세")
.navigationBarTitleDisplayMode(.inline)
}
}
// MARK: - 일정 추가 화면 (@Binding 사용)
struct AddEventView: View {
@Environment(\.dismiss) private var dismiss
/// 부모 뷰의 일정 배열을 직접 참조하는 바인딩
@Binding var events: [Event]
// 입력 폼 상태
@State private var title = ""
@State private var date = Date()
@State private var memo = ""
@State private var isAllDay = false
/// 제목이 비어있는지 확인
private var 제목비어있음: Bool {
title.trimmingCharacters(in: .whitespaces).isEmpty
}
var body: some View {
NavigationView {
Form {
Section("제목") {
TextField("일정 제목을 입력하세요", text: $title)
}
Section("날짜") {
Toggle("하루 종일", isOn: $isAllDay)
DatePicker(
"날짜",
selection: $date,
displayedComponents: isAllDay ? [.date] : [.date, .hourAndMinute]
)
}
Section("메모") {
TextField("메모를 입력하세요", text: $memo, axis: .vertical)
.lineLimit(3...6)
}
}
.navigationTitle("새 일정")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("취소") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("저장") {
// @Binding을 통해 부모의 배열에 직접 추가
let 새일정 = Event(
title: title.trimmingCharacters(in: .whitespaces),
date: date,
memo: memo.trimmingCharacters(in: .whitespaces),
isAllDay: isAllDay
)
events.append(새일정)
dismiss()
}
.disabled(제목비어있음)
}
}
}
}
}
// MARK: - 목록 화면
struct ContentView: View {
@State private var events = Event.샘플만들기()
@State private var showingAddSheet = false
var body: some View {
NavigationView {
List(events) { event in
NavigationLink(destination: EventDetailView(event: event)) {
EventRow(event: event)
}
}
.navigationTitle("내 일정")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: { showingAddSheet = true }) {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $showingAddSheet) {
// $events 바인딩을 자식 뷰에 전달
AddEventView(events: $events)
}
}
}
}
// MARK: - 앱 진입점
@main
struct MyScheduleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

7. 실행 체크
- [ㅇ] + 버튼을 눌러서 추가 화면을 열었다
- [ㅇ] 제목, 날짜, 메모를 직접 입력했다
- [ㅇ] 저장 버튼을 눌러서 목록에 추가했다
- [ㅇ] 취소 버튼도 눌러봤다
- [ㅇ] 실제로 내 일정을 3개 이상 추가했다
반응형
'AI 해보기 > 딸깍! 일정관리 앱 만들어보기' 카테고리의 다른 글
| 6. 필터 추가해보기 (카테고리 필터링) (1) | 2026.03.20 |
|---|---|
| 5. 카테고리와 알림 (0) | 2026.03.20 |
| 4. 삭제와 수정 추가 (CRUD 완성해보기) (0) | 2026.03.20 |
| 2. 상세화면 만들기 (0) | 2026.03.20 |
| 1. 일정관리 앱 만들기 준비 (0) | 2026.03.17 |
댓글