핀수로그
  • [SwiftUI] 인스타그램 UI 클론 코딩2
    2023년 02월 06일 22시 35분 04초에 업로드 된 글입니다.
    작성자: 핀수
    728x90
    반응형

     

    🚫 No ObservableObject of type FeedStore found. A View.environmentObject(_:) for FeedStore may be missing as an ancestor of this view.

    @EnviromentObject 어노테이션을 사용하면 별도의 초기화 없이 바로 해당 객체를 사용할 수 있다.

    그러나 이건 자동으로 생기는 것이 아니라, 어딘가에서 생성(인스턴스) 시켜줘야하는 거다.

    해당 뷰의 조상에서 인스턴스를 생성해줘야 한다는 의미 같은데

    현재 나는 FeedView에서 데이터를 사용하고 있다.

    FeedView의 조상은 MainView, MainView의 조상은 InstagramMainView 이다.

    따라서 InstagramMainView 에서 인스턴스를 생성하고,

    MainView에 envriomentObject로 넘겨주었다.

    struct InstagramMainView: View {
        @StateObject var store: FeedStore = FeedStore()
        @StateObject var storyStore = StoryStore()
        @StateObject var user: User = User(username: "pinslog", userProfile: "sample")
        
        var body: some View {
            VStack {
                TabView {
                    MainView()
                        .tabItem {
                            Label("", systemImage: "house")
                        }
                        .environmentObject(store)
                        .environmentObject(storyStore)
                        .environmentObject(user)
                    ...
                }
                ...
                }               
            }
        }
    }

    @EnviromentObject

    해당 어노테이션을 이용해 필요한 모든 곳에서 모델데이터를 공유할 수 있다.

    데이터가 변경될 때 뷰가 자동으로 업데이트 된 상태로 유지되도록 할 수 있다.

    @ObservedObject

    뷰A에서 일부 데이터를 생성한 다음, 뷰B, 뷰C, 뷰D 로 전달한 다음 최종적으로 사용하는 대신

    뷰A에서 생성하고 환경에 배치해 뷰B, C, D가 자동으로 액세스할 수 있다.

     

    Observable 프로토콜을 따르는 객체를 해당 어노테이션을 통해 정의할 수 있고,

    객체 내부의 프로퍼티는 UI를 통해 업데이트 되고 이때 @Published 가 업데이트 되었다는 신호를 쏴서

    다른 부분에서 변화를 알게 한다.

    @StateObject

    해당 어노테이션을 통해 관찰되고 있는 객체는 그들이 가지고 있는 화면 구조가 재생성 되어도 파괴되지 않는다.

    @ObservedObject 와 @StateObject

    • 관찰 중인 객체의 변경에 반응해 화면을 업데이트할 수 있게 해주는 SwiftUI의 프로퍼티 래퍼
    • Observable 프로토콜을 따르는 객체를 필요로 한다
      • 객체의 값이 바뀌기 전에 알려주는 퍼블리셔. SwiftUI가 화면을 다시 그리는 것을 가능하게 한다.

    ⇒ SwiftUI가 화면을 만들거나 다시 그릴 수 있는 가능성이 있는 경우에는 내부에 @ObservedObject 를 쓰는 것은 안전하지 않다.

    ⇒ 둘은 비슷한 특징을 가졌으나 SwiftUI가 둘의 라이프 사이클을 관리하는 방식에는 차이가 있다.

    @ObservedObject 를 사용하면 ObservedObject를 생성하는 화면에서도 일관된 결과 보장 가능

    의존 관계로 주입하는 거라면 @ObservedObject 사용 가능

    이미지를 외부에서 받아오기

    임의의 이미지를 불러올 수 있는 Lorem Picsum을 이용했다.

    그냥 Image 로는 통신을 통해 받아올 수 없었고, AsyncImage를 사용했다.

    AsyncImage
    A view that asynchronously loads and displays an image.

    AsyncImage 를 생성하기 위해서는 url, content, placeholder가 필요하다.

    placeholder는 content가 nil일 경우 또는 불러오는 동안 나타낼 이미지를 의미한다.

     

    예시 from 공식문서

    AsyncImage(url: URL(string: "https://example.com/icon.png")) { image in
        image.resizable()
    } placeholder: {
        ProgressView()
    }
    .frame(width: 50, height: 50)

    좋아요 UI 구현하기

    좋아요는 특정 버튼을 클릭하거나, 사진을 더블 클릭하면 좋아요의 갯수가 +1이 된다.

    좋아요 버튼 구현하기

    좋아요가 눌러진 상태에서 버튼을 클릭하면 좋아요는 취소가 된다.

    이말인즉슨, 버튼을 누른다고 좋아요 갯수가 계속해서 올라가면 안된다는 뜻이다.

    이를 그냥 구현한다고 생각하면…

    Bool 타입 변수를 하나 만들어서 false로 초기화 한다음,

    버튼의 상태가 true이면 false로 바꾸고, false면 true로 바꾸는 코드를 작성해야 했을 것이다.

    SwiftUI에서는 toggle() 이라는 메소드를 제공해준다. (똑같은 기능을 함)

    @State var isLiked = false
    
    MainButton(labelName: isLiked ? "heart.fill" : "heart", padding: buttonPadding) {
        isLiked.toggle()
        isLiked ? (feed.likeCnt -= 1) : (feed.likeCnt += 1)
    }
    .foregroundColor(isLiked ? .red : .black)

    사진 더블 탭 방식 구현하기

    버튼에 클릭리스너, 롱클릭리스너 달던거 기억하는지…?

    iOS에도 없을리 없다. 제스처라는 이름으로 돌아다니길래 관련해서 검색해봤다.

    View.gesture() 를 통해 각 제스처를 취할 때의 액션을 정의할 수 있었다.

    AsyncImage(url: URL(string: feed.imageUrl)) { ... }
    .gesture(TapGesture(count: 2).onEnded({ _ in
        likeAction(isLiked: isLiked)
    }))

    탭 제스처가 2번 감지 되었을 때 (onEnded) 아래의 코드를 실행한다.

    좋아요 기능은 버튼을 누르나 사진을 두번 탭하나 동일하기 때문에 메소드로 따로 빼줬다.

     

    공부하며 작성된 글이라 잘못된 정보가 있을 수 있습니다.

    말씀해주시면 수정하겠습니다. 감사합니다.

    References

    아래 글을 참고하여 작성 되었습니다.

    https://pilgwon.github.io/post/state-object-vs-observed-object

    https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views

    https://developer.apple.com/documentation/swiftui/asyncimage

    https://velog.io/@comdongsam/Jwords-%EC%8B%B1%EA%B8%80%ED%83%AD-%EC%A0%9C%EC%8A%A4%EC%B3%90%EC%99%80-%EB%8D%94%EB%B8%94%ED%83%AD-%EC%A0%9C%EC%8A%A4%EC%B3%90-%EB%8F%99%EC%8B%9C%EC%97%90-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B2%95

    https://www.notion.so/jungspin/SwiftUI-ca3941b0d4b74b6398400c56bf8cb13d#50d5e8e48634487fbdb13cd744dcf147

    728x90
    반응형

    'iOS' 카테고리의 다른 글

    [SwiftUI] 인스타그램 UI 클론 코딩  (0) 2023.02.03
    [iOS] 라이브러리 설치하기  (0) 2023.01.21
    댓글