런치스크린 먼저 구성해줌
이렇게도 구현해줄 수 있고 plist에 스토리보드 없이 추가하는 방법도 있다!
탭뷰를 먼저 구성해줬는데 tint칼라 하드코딩안하고 extension으로 빼주면 편할 거 같다
요렇게 Color의 익스텐션으로 static한 칼라들을 추가해줌
이제 탭뷰들에 맞는 실제 뷰들을 구성해주면 되겠다
title DisplayMode는 inline으로 설정함
struct PostView: View {
var body: some View {
VStack(alignment: .center, spacing: 0) {
//MARK: - Header
HStack {
Image("dog1")
.resizable()
.scaledToFill()
.frame(width: 30, height: 30)
.cornerRadius(15)
Text("User name here")
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.primary)
Spacer()
Image(systemName: "ellipsis")
.font(.headline)
}
.padding(6)
//MARK: - Image
Image("dog1")
.resizable()
.scaledToFit()
//MARK: - Footer
HStack(alignment: .center, spacing: 20) {
Image(systemName: "heart")
Image(systemName: "bubble.middle.bottom")
Image(systemName: "paperplane")
Spacer()
}
.font(.title3)
.padding(6)
HStack {
Text("This is the caption for the photo!")
Spacer(minLength: 0)
}
.padding(10)
}
}
}
크게 어려운 레이아웃은 없었다
강아지 너무 귀엽죠
model의 구조를 만들고 어떤 내용이 필요할지 생각해보자
post에 대한 ID가 필요할 것이고 user의 ID, name 그리고 caption!
date, like count, 어떤 유저에게 좋아요를 받았는지가 필요하겠다
import Foundation
import SwiftUI
struct PostModel: Identifiable, Hashable {
var id = UUID()
var postID: String // ID for the post in Database
var userID: String // ID for the user in Database
var username: String // Username of user in Database
var caption: String?
var dateCreate: Date
var likeCount: Int
var likedByUser: Bool
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
hash 메소드도 작성해줬는데 이건 추후에 알아보자
다시 PostView로 돌아와서 @State로 postModel을 선언해줌
그리고 프리뷰에 mock 데이터를 넣어줬다
그리고 PostView에 들어가는 내용들을 post 모델에서 가져온 데이터로 조금씩 수정해주자!
FeedView를 구성해주려면
PostModel을 배열로 가지고 있는 객체를 구성해줘야할 거 같다
import Foundation
class PostArrayObject: ObservableObject {
@Published var dataArray = [PostModel]()
init() {
print("Fetch from Database Here")
let post1 = PostModel(postID: "", userID: "", username: "WooZoo", caption: "This is a caption" , dateCreated: Date(), likeCount: 0, likedByUser: false)
let post2 = PostModel(postID: "", userID: "", username: "Jessica", caption: nil , dateCreated: Date(), likeCount: 0, likedByUser: false)
let post3 = PostModel(postID: "", userID: "", username: "Steve", caption: "This is a really really long caption hahahahahaha" , dateCreated: Date(), likeCount: 0, likedByUser: false)
let post4 = PostModel(postID: "", userID: "", username: "Emily", caption: "This is a caption" , dateCreated: Date(), likeCount: 0, likedByUser: false)
self.dataArray.append(post1)
self.dataArray.append(post2)
self.dataArray.append(post3)
self.dataArray.append(post4)
}
}
init될 때 dataArray에 PostModel들이 추가되게끔 해줌
FeedView에서 ForEach를 사용해서 PostView들을 그려주고
근데 여기에 posts 갯수가 많아지면 다 로드하게 되니까 LazyVStack으로 감싸줘야함
🤔 @ObservedObject로 가져온 posts들을 ForEach로 그리는 부분 갑자기 아리송하다
잘 알고 있는 줄 알았는데 막상 이렇게보니 헷갈림
PostView로 넘겨주는 작업에서 Binding으로 선언하는 프로퍼티가 없어서 더 헷갈리는 듯
struct CommentsView: View {
@State var submissionText: String = ""
var body: some View {
VStack {
ScrollView {
Text("Placeholder")
Text("Placeholder")
Text("Placeholder")
Text("Placeholder")
}
HStack {
Image("dog1")
.resizable()
.scaledToFill()
.frame(width: 40, height: 40)
.cornerRadius(20)
TextField("Add a comment here...", text: $submissionText)
Button {
} label: {
Image(systemName: "paperplane.fill")
.font(.title2)
}
.tint(Color.MyTheme.purpleColor)
}
.padding(6)
}
.navigationTitle("Comments")
.navigationBarTitleDisplayMode(.inline)
}
}
ScrollView에선 댓글들이 보일 거고,
그 아래로 댓글을 다는 뷰를 구성해줌
struct MessageView: View {
var body: some View {
HStack {
Image("dog1")
.resizable()
.scaledToFill()
.frame(width: 40, height: 40, alignment: .center)
.cornerRadius(20)
VStack(alignment: .leading, spacing: 4) {
Text("UserName")
.font(.caption)
.foregroundColor(.gray)
Text("This is a new comment here.")
.padding(10)
.foregroundColor(.primary)
.background(Color.gray)
.cornerRadius(10)
}
Spacer(minLength: 0)
}
}
}
postModel을 만들고 post를 구성해줬던 것처럼 MessageModel이 필요하다
import Foundation
import SwiftUI
struct CommentModel: Identifiable, Hashable {
var id = UUID()
var commentID: String // ID for the comment in the Database
var userID: String // ID for the user in the Database
var username: String // Username for the user in the Database
var content: String // Actually comment text
var dateCreated: Date
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
앞서 만들었던 PostModel과 구조가 상당히 비슷하죠
MessageView로 다시 와서 @State로 CommentModel 선언해주면 끝
CommentsView로 돌아와서 앞서 FeedView에서 구성해줬던 것처럼
ScrollView를 구성해줌
이때 commentArray라는 CommentModel 배열을 가지고 MessageView를 그려준다!
처음엔 빈배열로 설정했는데
.onAppear될 때 getComment 메소드가 호출되게 해주고
지금 당장은 getComment에 임시 데이터들을 만들어줌
PostView에서 댓글 버튼을 누르게 되면 navigate되게 해주면 될 것같다
요렇게!
Browse뷰를 만들어보자
struct BrowseView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
Text("placeholder")
}
.navigationTitle("Browse")
.navigationBarTitleDisplayMode(.inline)
}
}
여기 ScrollView에 보면 Text가 임시로 들어가 있는데 이 뷰를 그 사진 넘기는 페이징뷰로 바꿔줄 예정
CarouselView라는 subview를 구성해주자
struct CarouselView: View {
@State var selection: Int = 1
let maxCount: Int = 8
@State var timerAdded: Bool = false
var body: some View {
TabView(selection: $selection) {
ForEach(1..<maxCount) { count in
Image("dog\(count)")
.resizable()
.scaledToFill()
.tag(count)
}
}
.tabViewStyle(.page)
.frame(height: 300)
.animation(.default, value: selection)
.onAppear {
if !timerAdded {
addTimer()
}
}
}
//MARK: - Functions
func addTimer() {
timerAdded = true
let timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { timer in
if selection == maxCount - 1 {
selection = 1
} else {
selection += 1
}
}
timer.fire()
}
}
TabView 스타일을 page로 구성해주고, ForEach에선 Image를 그려준 다음에 .tag를 붙여줬다
selection이 자동으로 변하게 해주는 로직을 추가하기 위해서
timer를 구성해주고 .onAppear에 추가해주는데 두번 onAppear되는 걸 방지하기 위해서
timer가 추가됐는지를 확인하고 addTimer가 되게끔 해줌
struct ImageGridView: View {
var body: some View {
LazyVGrid(
columns: [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
],
alignment: .center,
spacing: nil,
pinnedViews: []) {
Text("Placeholder")
Text("Placeholder")
}
}
}
ImageGridView를 만들어줬다
이때 들어가는 아이템의 로직은 PostView랑 동일한데 이미지만 보여줄 예정
새로운 View를 구성하기보단 기존에 있는 PostView를 조금 수정해보자
showHeaderAndFooter를 나타낼 Bool값을 만들고
이 값이 참일 때만 헤더와 푸터를 보여주게 구성해줌
다시 ImageGridView로 돌아와서 ForEach로 post를 구성해주면 되겠죠
그리고 BrowseView에서 Carousel아래에 만들어주면됨!
이미지 클릭하면 post로 넘어가게도 해주고 싶음
-> NavigationLink 삽입
근데 FeedView처럼 보여주고 싶고, 선택된 Post만 보여주고 싶음
PostArrayObject에서 새로운 init을 만들어주자
post만 보여주는 init을 만들어줌!
그리고 destination 설정해주면 되겠죠
FeedView navigationTitle에 변수로 값을 넘겨줘서 진입할때 상황에 맞게 title도 바꿔줌
🤔지금 PostArrayObject를 각각 다른 init으로 초기화해주면 dataArray에는 중첩해서 쌓이는 게 아닌 것 같음. 어떤 로직으로 구성이 되는 원리인지 제대로 알아야할 듯
struct UploadView: View {
var body: some View {
ZStack {
VStack(spacing: 0) {
Button {
} label: {
Text("Take photo".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(Color.MyTheme.yellowColor)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.MyTheme.purpleColor)
Button {
} label: {
Text("Import photo".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(Color.MyTheme.purpleColor)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.MyTheme.yellowColor)
}
Image("logo.transparent")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100, alignment: .center)
.shadow(radius: 12)
}
.edgesIgnoringSafeArea(.top)
}
}
크게 어려운 layout은 없음
UIImagePickerController를 사용할 예정
UIKit에 있는거라 SwiftUI로 바꿔줘야한다
import Foundation
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
@Environment(\.dismiss) var dismiss
@Binding var imageSelected: UIImage
@Binding var sourceType: UIImagePickerController.SourceType
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = sourceType
picker.allowsEditing = true
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
func makeCoordinator() -> ImagePickerCoordinator {
return ImagePickerCoordinator(parent: self)
}
class ImagePickerCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage ?? info[.originalImage] as? UIImage {
// select the image for our app
parent.imageSelected = image
// dismiss the screen
parent.dismiss()
}
}
}
}
홀리몰리 먼소린지 하나도 모르겠다
UIViewController 객체를 만들고 SwiftUI에서 사용할 때 쓰는 프로토콜
프로토콜만 채택한 채로 사용하면 SwiftUI 인터페이스가 변경사항들을 알아차리지 못해서 꼭 Coordinator 인스턴스를 제공해줘야함
구현해줘야할 메소드에는 makeUIViewController랑 updateUIViewController가 있다.
makeUIViewController를 이용해서 UIController 객체를 만들어주게 됨
이때 context라는 파라미터는 저거임
필요한 Delegate 들을 가지고 있는 class 를 ImagePickerCoordinator로 만든 다음에
delegate에 있는 메소드들을 custom해줌
보면 imagePickerController 메소드가 그거임
이 클래스에 대한 부모를 나자신으로 설정해주면
@Binding 프로퍼티 래퍼로 구성해준 애들이 SwiftUI에서 제대로 알아차리게됨
다시 UploadView로 돌아와서
.sheet으로 뷰를 띄워줄거다
그리고 버튼들에 로직 추가해주면 되겠죠
근데 이거 실행해서 확인해보기전에 해줘야할게 있음
앱 처음 실행하면 묻는거 있잖음
이 앱에서 카메라를 사용할건데 허용할건지 같은거
여기서 추가해줘야함
Camera usage랑 Photo Library Usage 추가해주기!
런 해도 허용 묻는 창은 안나오고 있음 어떻게 된거지.. 실제 app에선 보이려나
PostImageView를 만들거임
import SwiftUI
struct PostImageView: View {
@Environment(\.dismiss) var dismiss
@State var captionText: String = ""
@Binding var imageSelected: UIImage
var body: some View {
VStack(alignment: .center, spacing: 0) {
HStack {
Button {
dismiss()
} label: {
Image(systemName: "xmark")
.font(.title)
.padding()
}.tint(.primary)
Spacer()
}
ScrollView(.vertical, showsIndicators: false) {
Image(uiImage: imageSelected)
.resizable()
.scaledToFill()
.frame(width: 200, height: 200, alignment: .center)
.cornerRadius(12)
.clipped()
TextField("Add your caption here...", text: $captionText)
.padding()
.frame(height: 60)
.frame(maxWidth: .infinity)
.background(Color.MyTheme.beigeColor)
.cornerRadius(12)
.font(.headline)
.padding(.horizontal)
.textInputAutocapitalization(.sentences)
Button {
postPicture()
} label: {
Text("Post Picture!".uppercased())
.font(.title3)
.fontWeight(.bold)
.padding()
.frame(height: 60)
.frame(maxWidth: .infinity)
.background(Color.MyTheme.purpleColor)
.cornerRadius(12)
.padding(.horizontal)
}.tint(Color.MyTheme.yellowColor)
}
}
}
//MARK: - Functions
func postPicture() {
print("Post picture to database here")
}
}
struct PostImageView_Previews: PreviewProvider {
@State static var image = UIImage(named: "dog1")!
static var previews: some View {
PostImageView(imageSelected: $image)
}
}
PostImageView에선 선택된 Image를 가져와야한다
UIImage를 받을 수 있게 준비해줬음
UploadView의 로고 아이콘에 .fullScreenCover를 달아줌
기존에 있던 .sheet모디파이어에 onDismiss 프로퍼티도 추가하고 segueToPostImageView라는 메소드를 이용해서 .fullScreenCover가 뜰 수 있게 해줬다
근데 지금의 로직은 동시에 진행되면 꼬일 수 있어서 DispatchQueue를 이용해서 살짝의 Delay도 줬다!!
프로필뷰를 만들어봅시다!!
프로필뷰를 대략적으로 구성해줌
이때 .toolbar [이게 현재의 표현임. 예전엔 .navigationBarItems로 붙여줬었음]
로 툴바아이템을 붙여준다
그리고 ScrollView에 들어가게 될 헤더뷰를 subView로 구성해줌
를 해야하는데 이거 지금 정리하는 게 의미가 없다는 판단.
UI 구현부분 다 완전히 손에 익을 때까지는 패스..!
5번 반복해서 구조 만드는 거 연습합시다