-
swift 로 메모장 만들기 따라하기IOS 2023. 12. 14. 17:06
SWIFT 언어를 공부하기 시작하면서, 유투브 강의를 하나 따라 해보았다.
https://www.youtube.com/watch?v=O7fZ2ZvEKoA
xcode에서 swift를 써본적이 없고 기본적인 문법의 지식이 없었지만 swift를 활용하여 간단한 메모앱을 만들 수 있었고
react를 많이 사용해보지 않은 나로서는 공부하기 좋은 동영상이었다.이 동영상에서는 Swift UI를 사용했다. Swift UI는 Swift 언어를 이용해서 쉽게 프로덕트를 제작하기 위해서 개발한 프레임워크이다.
해당 프레임워크를 활용해서 제작한 어플의 경우 네이티브 앱이라고 볼 수 있다.
이후에는 회사의 상황에 맞춰서 UI kit 과 cocoapots에서 다운받은 pots 를 활용하여 앱을 개발할 것 같다.
1. 메모 객체 생성다음과 같이, swift 프로잭트를 생성하니 App과 Test 폴더들이 생성됐다.
이후에 View / Memory / CoreData 폴더를 생성했다.
CoreData에는 프로젝트 생성 시 설정했던 CoreData 로 인해 생성된 두 파일을 넣어뒀다.
아직 자세히는 모르지만, 하나는 DB를 이용할 수 있도록 하는 장치이고 하나는 간편한 DB 인 것 같다.메모장이므로, 메모와 메모들을 배열 형태로 저장할 수 있는 Store를 생성했다.
import Foundation import SwiftUI class Memo: Identifiable, ObservableObject { let id: UUID @Published var content: String let insertDate: Date init(content:String , insertDate: Date = Date.now){ id = UUID() self.content = content self.insertDate = insertDate } }
Memo의 경우, id 와 내용, 날짜 3가지의 필드를 가지고 있고, 날짜의 경우 값이 없다면 현재 날짜로 저장되도록 되어있다.
ObservableObject의 경우 옵저버 옵션이 적용되어있어서 해당 객체에 변화가 생기면, 객체와 연결된 다른 요소들에 이벤트가 발생하게 만드는 것 같다.
@Published 로 선언한 변수의 경우, 변수의 값이 연동되고 있는 것 같다.
import Foundation class MemoStore: ObservableObject { @Published var list: [Memo] init(){ list = [ Memo(content: "Fist Content", insertDate: Date.now), ] } func insert(memo: String){ list.insert(Memo(content: memo), at: 0) } func update(memo: Memo?, content: String){ guard let memo = memo else { return } memo.content = content } func delete(memo: Memo){ list.removeAll { $0.id == memo.id } } func delete(set: IndexSet){ for index in set { list.remove(at: index) } } }
MemoStore의 경우 Memo들을 저장할 수 있는 배열이 있고, init 할 때 list를 초기화 한다.
또한 list에 memo를 추가, 수정, 삭제 할 수 있는 function 들이 있다.
모델과 컨트롤러를 잘 설계한 것 같다.
2. 메인 뷰 생성
import SwiftUI @main struct SwiftUIMemoApp: App { @StateObject var store = MemoStore() let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { MainListView() .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(store) } } }
위의 코드는 앱의 시작점으로, MemoStore 를 초기화하고 메인 뷰를 불러온다.
특이한 점으로는 어노테이션을 StateObject로 걸고 변수를 선언했다. swift에서는 enum처럼 타입에 엄격한 경우들이 많은 것 같은데, 이 부분은 typescript를 사용하는 것 같은 느낌이 든다.
View를 그릴 때는, Scene 위에 View들을 쌓는 느낌이다.
body를 Scene 타입으로 생성 한 후에, 다른 view 파일을 불러서 View타입의 body들을 나열한다.
메인 화면을 그릴 MainListView 에는 .environmentObject(store)를 함으로서, MemoStore를 사용할 수 있게 만들었다.
persistenceController와 .environment의 경우, CoreData와 관련있어 보이는데 자세한 것은 다음에 알아볼 것이다.import SwiftUI struct MainListView: View { @EnvironmentObject var store: MemoStore @State private var showComposer: Bool = false var body: some View { NavigationView { List { ForEach(store.list){ memo in NavigationLink{ NavigationView{ DetailView(memo: memo) } } label: { MemoCell(memo: memo) } } .onDelete(perform: store.delete) } .listStyle(.plain) .navigationTitle("내 메모") .toolbar { Button { showComposer = true } label: { Image(systemName: "plus") } } .sheet(isPresented: $showComposer){ ComposeView() } } } } #Preview { MainListView() .environmentObject(MemoStore()) }
메인 뷰 파일은 다음과 같다.
먼저, MemoStore를 사용할 수 있게 필드에 저장하고, Body 안에 요소들을 추가했다.
특이한 점 1번으로는대부분 swift에서 제공하는 명령어로 제작했다는 점이다.
NavigationView 는 앱 화면에 Toolbar 또는 Navigation 요소에 대한 설정을 할 수 있게 해준다.부트스트랩을 사용하고 있는 것 처럼, NavigationView안의 공간을 세밀하게 분류해둔 것 같다.
특이한 점 2번으로는 React처럼 컴포넌트에 . 을 붙이는 것으로 뷰를 그린다.
swift에 저장되어 있는 변수를 불러올 때 .Name 형태로 불러오던데, 헷갈리지 않게 조심 해야할 것 같다.
필드에서 선언한 변수를 불러올 때는 $ 를 앞에 붙인다
특이한 점 3번은 버튼 태그에 이벤트를 걸 때, $State 변수를 미리 선언해두고, isPresented 에 해당 변수를 걸어두고 사용한다는 점이다.
아직 이 방식에 대해 완벽하게 이해한 것은 아니지만, 이벤트를 만들 때 마다 변수를 선언하면 복잡한 기능의 경우 변수가 너무 많을 것 같기도 하고 관리를 깔끔하게 하는 것 같기도 하고 반반이다.
마지막으로 #Preview 를 이용하여 시뮬레이터를 켜면, 현재 뷰가 어떻게 작업되고 있는지 확인할 수 있다.
3. 그 외 작은 뷰 들
import SwiftUI struct MemoCell: View { @ObservedObject var memo: Memo var body: some View { VStack(alignment: .leading){ Text(memo.content) .font(.body) .lineLimit(1) Text(memo.insertDate, style: .date) .font(.caption) .foregroundColor(.secondary) } } } #Preview { MemoCell(memo: Memo(content: "Test")) }
MemoCell은 메인 뷰의 List에 들어갈 요소들이다. HTML로 따지면 tr 태그 같다.
요소를 선언하고 .명령어를 추가하는 것으로 코드를 구성하니 매우 깔끔하다.다만, 반대로 여러가지 코드들을 숙지하고 있어야 편하게 사용할 수 있을 것 같다
import SwiftUI struct ComposeView: View { @EnvironmentObject var store: MemoStore var memo: Memo? = nil @Environment(\.dismiss) var dismiss @State private var content: String = "" var body: some View { NavigationView { VStack { TextEditor(text: $content) .padding() .onAppear { if let memo = memo { content = memo.content } } } .navigationTitle(memo != nil ? "메모 편집" : "새 메모") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { Button { dismiss() } label: { Text("취소") } } ToolbarItemGroup(placement: .navigationBarTrailing) { Button { if let memo = memo { store.update(memo: memo, content: content) } else { store.insert(memo: content) } dismiss() } label: { Text("저장") } } } } } } #Preview { ComposeView() .environmentObject(MemoStore()) }
ComposeView는 메모 작성 공간이다. 주로 .sheet를 사용하여 모달 형태로 불러오도록 사용되었다.
이 때 사용된 dismiss() 는 주로 모달창이나 시트를 닫을 때 사용되고, NavigationLink로 이동한 경우에도 이전 화면으로 돌아갈 수 있는 기능이다.
이 뷰는 다른 것 보다, memo의 상태에 따라서 텍스트와 기능이 변하는 부분을 깔끔하게 설계한 것 같다.
import SwiftUI struct DetailView: View { @ObservedObject var memo: Memo @EnvironmentObject var store: MemoStore @State private var showComposer = false @State private var showDeleteAlert = false @Environment(\.dismiss) var dismiss var body: some View { VStack { ScrollView { VStack { HStack { Text(memo.content) .padding() Spacer(minLength: 0) } Text(memo.insertDate, style: .date) .padding() .font(.footnote) .foregroundColor(.secondary) } } } .navigationTitle("메모 보기") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .bottomBar) { Button { showDeleteAlert = true } label: { Image(systemName: "trash") } .foregroundColor(.red) .alert("삭제 확인", isPresented: $showDeleteAlert){ Button(role: .destructive){ store.delete(memo: memo) dismiss() } label: { Text("삭제") } } message: { Text("메모를 삭제할까요?") } Button { showComposer = true } label: { Image(systemName: "square.and.pencil") } } } .sheet(isPresented: $showComposer){ ComposeView(memo: memo) } } } #Preview { NavigationView { DetailView(memo: Memo(content: "Hello")) .environmentObject(MemoStore()) } }
상세보기 페이지 이다. 이 페이지를 보면 확실히 여러가지 명령어들을 다 알고 있어야 개발하기에 용이할 것 같다는 생각이 들었다.
4. 마무리
mac과 xcode를 사용하면 좋은 점은 기본적으로 탑제하고 있는 단축키와 기능이 많다는 것이다.
특히 특정 Element에 우클릭 또는 shift + command + A 를 눌러서 Quick Action을 켜면,특정 요소가 사용되고 있는 모든 문서의 이름을 변경하는 Rename이나,
요소에 Container를 생성하는 Embed,여러가지 Stack을 생성하는 기능 등 개발에 용이한 기능들이 많다.
5. 아이폰에 내려받기
Xcode의 상단에 있는 메뉴 탭에서 Mac과 열결된 I phone을 선택하고, build 버튼을 클릭하면 핸드폰에 어플을 다운받을 수 있다.
물론, 그 전에 Xcode 에서 Apple계정과 iPhone의 신뢰성 인증을 받아야하는 번거로움이 있지만, 그만큼 보안이 철저한 것 같다.
'IOS' 카테고리의 다른 글
mac에서 httpd 찾기 (0) 2023.12.14