info.plist에
폰트 Value 추가해주고
네비게이터에 추가해주면 됨
struct SignInWithAppleButtonViewRepresentable: UIViewRepresentable {
let type: ASAuthorizationAppleIDButton.ButtonType
let style: ASAuthorizationAppleIDButton.Style
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
ASAuthorizationAppleIDButton(authorizationButtonType: type, authorizationButtonStyle: style)
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) { }
}
AppleButton 커스텀 한걸로 쓰고 있었는데
이거 텍스트가 현지화가 안되는 거 같음
struct CustomSignInWithAppleButton: View {
var action: () -> Void
var body: some View {
Button(action: action) {
HStack {
Image(systemName: "applelogo")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
// .padding(.leading)
Text("Apple로 시작하기")
.font(.headline)
.padding(.trailing)
}
}
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.black)
.cornerRadius(6)
}
}
그냥 커스텀하게 만들어줌
포스팅 구현 완료!
포스팅 삭제도 구현해줘야겠다!
(이 포스팅 삭제를 구현하는 방법엔 두 가지가 있음)
실제로 삭제를 하는 것과 다른 것
username DBUser꺼 업뎃하고
DBUser 가지고 와서 username으로 다시 post등록할 때 넣어줌!
쉬울 줄 알았는데 생각보다 할게 조금 있다..
user가 like한 내역을 DBUser 콜렉션에서도 저장을 해주고
이 사람이 좋아요한 걸 추적 해야함
좋아요는 완료 했는데
포스트가 삭제 되면 좋아요했던 post도 같이 삭제되야함
동시에 좋아요할 때 값 업데이트에 문제가 있었음!
func updatePostsLikeCount(postId: String, increment: Int) async throws -> Int {
var newLikeCount = 0
try await Firestore.firestore().runTransaction { transaction, errorPointer -> Any? in
let postDocument = self.postDocument(uid: postId)
do {
let snapshot = try transaction.getDocument(postDocument)
guard let oldCount = snapshot.get("like_count") as? Int else {
throw NSError(domain: "AppErrorDomain", code: -1, userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve like count from snapshot"
])
}
newLikeCount = oldCount + increment
transaction.updateData(["like_count": newLikeCount], forDocument: postDocument)
} catch {
errorPointer?.pointee = NSError(domain: "AppErrorDomain", code: -1, userInfo: [
NSLocalizedDescriptionKey: "Transaction failed: \(error.localizedDescription)"
])
}
return nil
}
return newLikeCount
}
PostManager의 메소드에서 updatePosts 라잌카운트 메소드에 transaction을 추가해줬다
이제 코멘트 작업 해봅시다
user id 비교해서 삭제 수정
삭제는 간단하고,
수정은 조금 있네
으어어어어
엄청 간단한거였는데...
한참을 헤맸다
PostModel에 edited 되었는지 표시하는 거 추가해줍시다!!
Shimmer 뷰
.redacted(reaspon: .placeholder)로도 가능한데
그냥 로직 상에서 로딩 중인지 판단해서 ShimmerView 띄워주는 걸로
당겨서 새로고침 -> .refreshable로 구현 가능
댓글 카운트 업데이트
다운받아 올 때 전체 가져오는 거 말고 뷰에 보여질 것 만 가져오는 거
PostArrayObject에 downloadPosts, isLoading 업뎃시켜줘야함
Query 자체에 limit을 걸고 마지막 document를 저장한 다음에 거기서부터 다시 Query를 걸어서 pagination을 구현할 수 있는데
Firebase 유튭 영상 보니까 두세가지 더 방법이 있긴함
.offset으로 여유분까지 로드 하는 방법도 있고,
limit 갯수를 10, 20, 30, 40 늘려가는 방법도 있음(이러면 pagination의 의미가 없긴하지만 ㅋㅋㅋ)
지금 ForumView가 onAppear될 때 posts를 다운로드 하는 로직이 실행되고 있는데
ShimmerRowView보여주려고 분기 처리했던 것 때문에
하루를 날렸다..!!
isLoading이 토글되면 if else 쪽에 뷰가 껐다 켜지니까 이게 무한한 스크롤을 만들어내버림
결국에 Scroll할 때 가져오는 메소드에는 애니메이션 토글을 안해주는 걸로 해결함
글구 전체 콜렉션 카운트 해서 dataArray 카운트랑 같으면 opacity로 마지막 ProgressView 숨기는 것도 해줌
Liking Posts가 ForumView에서 정상적으로 표시 안되는 문제가 있다
뭐가 문제였는지 쪼끔은 알거 같음
ForumView -> PostRowView -> PostView 이렇게 전달 되는 post 데이터들이 연결이 안되어 있었음
Binding으로 다 묶일 수 있게 처리하고 나니까 잘 됨!
ForumView에서 ForEach로 posts array 그려줄 때 Binding 가능한 데이터를 전달하는 게 안되는 줄 알았는데
ForEach(vm.posts.indices, id:\.self) { index in
PostRowView(post: $vm.posts[index])
if vm.posts[index] == vm.posts.last {
ProgressView()
.onAppear {
vm.getMorePosts()
print("--->Getting More Posts")
}
.opacity(vm.isFull ? 0 : 1 )
}
}
요렇게 index 가지고 전달하면 가능함!!
좋아요 처리
https://fomaios.tistory.com/entry/파이어베이스-트랜잭션Firebase-Transaction-Swift
firestore에 저장되는 document제목 그냥 UUID().uuidString으로 저장되게 해줌 이렇게 통일하는 게 나을 거 같다.
이제 comment 콜렉션에 listener추가되게 해보자
.disappear일 때 정상적으로 listener해제 되는지 확인해야함
comment를 지금 listener로 처리하고 있었는데 이 과정에서
update하는 commentCount에 약간의 오차가 있었다.
음.. 1개씩 계속 적게 찍힌걸보면
comment가 firestore에 올라가기전에 post의 comment 카운트가 업데이트 되서 그런것 같기도 하고!
그래서 그냥 +1 더해서 업뎃 되게 해줌
순서 맞추는 게 정답이었을 거 같은데 비동기 처리하는 과정이 약간 알쏭달쏭이라
지금은 comments count에 1 더해서 업뎃해줍시다
내 코멘트면 수정이 가능해야함
그 전에 내 코멘트면 우측에 나오게끔 디자인 바꿔주자
🤔
- 닉네임 변경하고 댓글 입력 시에 입력한 댓글이 업데이트 바로 되지 않는 현상
해결필요- 카톡처럼 스크롤 위에 있을 때 새로 입력한 댓글 보이면 알림처럼 뜨고,
제일 밑에 있을 경우엔 스크롤이 올라가게 구현하기
ScrollViewReader 구현하기
-> 구현완료 ScrollViewReader의 .id 값은 comment의 id값으로 사용함
포스트 뷰 진입 시점에서 auth유저를 검사하는 과정이 늦게 나오는지
상대댓글 뷰에서 내 댓글 이동하는 게 보임 -> user가 nil일 때 안 보이게 하는 걸로 해결
카테고리가 될 이미지는
coin.image의 string URL이고,
코인 뷰 모델에서 의존성 주입으로 dataService에 coin 넘겨주고,
dataService인 CoinImageService는 init되는 시점에서 전달받은 coin image url로 download 처리를 해준다!
download는 NetworkingManager가 처리해주고!
.toolbarBackground(.visible, for: .tabBar)
이거 탭마다 다 붙여주면 해결됨
카테고리는 활성화 시킬 게시판지기가 있는 만큼만 만들어둠
웹뷰로 노션 페이지 가지게?
생각보다 간단하다!!!
struct WebView: UIViewRepresentable {
var url: URL
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let request = URLRequest(url: url)
uiView.load(request)
}
}
UIViewRepresentable로 WKWebView를 SwiftUI에서 쓸 수 있는 View 형태로 바꿔줌
import SwiftUI
import WebKit
struct ContentView: View {
@State private var showWebView = false
private let urlString: String = "https://mandarin.oopy.io"
var body: some View {
VStack(spacing: 40) {
WebView(url: URL(string: urlString)!).frame(height: 500)
.shadow(color: .black.opacity(0.3), radius: 20, x: 5, y: 5)
Link(destination: URL(string: urlString)!) {
Text("Open in a new window")
.foregroundColor(.blue)
}
Button("Open in a sheet") {
showWebView.toggle()
}
.sheet(isPresented: $showWebView) {
WebView(url: URL(string: urlString)!)
}
Spacer()
}
}
}
그리고 나서 첨부터 뷰가 뜨게 해주고 modifier로 shadow 같은 것도 넣어줄 수 있고, Link써서 safari에서 뜨게 해줄 수도 있고,
.sheet써서 새로운 시트가 나오게 해줄 수도 있다!
활용해서 웹뷰로 우피 링크 연결되게!
클래스 모델 나중에는 Firestore에 저장해야함
작업 진행하다 고민이 조금 더 추가됐다..!!
로그인 시에 온보딩 페이지 단계를 추가해서 유저의 정보를 초기에 세팅이 가능하게 해야된다는 걸 깨달음
지금은 로그인 하면 바로 프로필로 넘어가는데
중간에 온보딩 뎁스가 하나 더 생겨야함
온보딩 항목 인지해두기!
우선은 프로필뷰를 완성하는 것에 주력을 하자
유저의 정보를 업데이트해줄 수 있는 뷰들이 필요하다
간단하게 닉네임
Github 아이디
로그아웃
계정삭제
이렇게만 구성
github 아이디도
DBUser에 저장 추가해줘야겠다
textField 터치로 해제하는 거 ZStack에다 백그라운드 넣어주고
.onTapGesture일 때 FocusState 변수 false로 만들어주면 된다!
지금 로그아웃 했다가 다시 로그인할때 가지고 있던 정보들이 날라가는 현상이 생김
- user가 새로 생성되고 있는 것 같다.
final class UserManager {
func userExists(userId: String) async throws -> Bool {
let document = try await userDocument(userId: userId).getDocument()
return document.exists
}
}
userExists라는 메소드 구성해주고 userId를 바탕으로 document를 가져와봤을 때 존재하면 참이게 해줌
@MainActor
final class AuthenticationViewModel: ObservableObject {
func signInGoogle() async throws {
let helper = SignInGoogleHelper()
let tokens = try await helper.signIn()
let authDataResult = try await AuthenticationManager.shared.signInWithGoogle(tokens: tokens)
let user = DBUser(auth: authDataResult)
let userExists = try await UserManager.shared.userExists(userId: user.userId)
if !userExists {
try await UserManager.shared.createNewUser(user: user)
}
}
}
그리고 userExists가 false일 때만 createNewUser가 실행되게!
ProfileLoggedin View로 바꾸면서 이 로직이 빠져버렸나보다
지금 로직이 살짝 꼬인 것 같다.
로그인 뷰에서 로그인을 할 때 User가 존재하는지 userDocument에 authResult로 받은 토큰의 userId를 가지고 document 존재 유무를 판별하는데 로그인이 완료되고 ProfileLoggedInView로 왔을 때 loadCurrentUser를 task로 처리해주고 있음
🤔 이렇게 계정 존재유무를 판별하는게 맞을까?!
괜히 쿼리 하나가 더 늘어난 느낌이 든다...
닉네임 업데이트 하던 것과 마찬가지로 깃허브 업뎃 구성해줌
근데 하드코딩이 되버렸다. 리팩토링 필요함
세팅뷰에서 계정 삭제 버튼을 누르면
네비게이션으로 계정 삭제 하는 페이지가 뜨게 한다.
계정 삭제를 누르면 Auth.auth() 에서 현재 유저에 대한 정보를 delete() 해주고,
이제 생각해볼건 실제로 UserCollection에서 삭제를 해줄 것인지다!
Flag처리 해주는걸로!
delete할 때 에러가 발생하는 경우가 있음!
--> 이 행동은 로그인을 다시해서 인증이 필요하다~
재 로그인에 대한 처리가 필요하겠습니다!
AsyncImage는 어떻게 reload를 해줄까?
우선은 기존에 있던 이미지를 storage에서 삭제가 되게끔 해주자
AsyncImage를 reload하는 방법을 찾아야겠다..
킹피셔나 누크는 안쓰고 구현을 해보고 싶다는 생각.
쉽게 해결함.
단순히 저장할 때 currentUser를 업데이트 해주는 방법으로 해결했습니다!!
//MARK: - Profile 업뎃
func saveProfileImage(item: PhotosPickerItem) {
guard let user else { return }
Task {
guard let data = try await item.loadTransferable(type: Data.self) else { return }
let (path, name) = try await StorageManager.shared.saveImage(data: data, userId: user.userId)
print("Success!")
print(path)
print(name)
let url = try await StorageManager.shared.getUrlForImage(path: path)
try await UserManager.shared.updateUserProfileImagePath(userId: user.userId, path: path, url: url.absoluteString)
try await self.loadCurrentUser()
}
}
🤔이미지 크기가 클 경우에 업로드가 안된다는 에러 창 보내기
userId만 가지고 콜렉션에서 서치해서 url 가지고 오는걸로
이걸 구현하려고 보니
settingsView에 사용된 거,
프로필 로그인드View에 사용된 거,
PostView에 사용될 거
하나의 이미지 컴포넌트로 만들어주면 좋을 것 같죠!
userId를 가지고 profile_image_path_url을 가져온 후에
Async 이미지 처리를 해주면 될 것 같습니다
.onAppear {
Task {
try await vm.loadCurrentUser()
try await vm.loadComments(postId: vm.post.id)
vm.isLiked = try await UserManager.shared.userHasLikedPost(userId: vm.user?.userId ?? "n/a", postId: vm.post.id)
}
Task {
try await vm.getUserImageURL()
}
}