We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
현재 사용자가 입력한 결과값에 따라 Media List를 받아오고 각 Cell을 클릭하면 DetailView로 Push 되고 있는 형태의 앱이다.
struct MediaListView: View { @StateObject var viewModel: MediaListViewModel let appContainer: AppContainer var body: some View { NavigationView { ScrollView { LazyVStack { ForEach(viewModel.output.medias, id: \.id) { media in NavigationLink( destination: { NavigationLazyView( MediaDetailView( viewModel: appContainer.mediaDetailViewModel(media) ) ) }, label: { MediaListItemView(media: media) } ) } } .padding(.top, 20) .padding([.leading, .trailing], 12) } .navigationTitle("TV Search") .navigationBarTitleDisplayMode(.inline) .searchable(text: $viewModel.input.searchMediaSub.value) } } }
SwiftUI의 NavigationLink는 사용자가 push하기 이전뷰에서도 push될 뷰를 미리 load하고 있는 문제점(?)이 있는데 이를 방지하고자 push되는 시점에서 뷰가 그려질 수 있도록 NavigaitonLazyView를 사용하고 있다.
하지만 위와 같이 코드를 작성하고, 기존에 리스트에 있는 cell을 클릭하는 시점에 사용자가 키보드를 통해 다른 값을 입력하면 자동으로 navigation pop이 되는 문제점이 있다.
입력값이 변화함에 따라서 기존의 list가 가지고 있던 cell이 사라지면서 navigationLink도 사라져 버린것이다!
이런 문제가 발생하는 이유는 list의 cell이 navigationLink라는 View로 만들어졌기 때문이다. 따라서 문제를 해결하기 위해서 List의 cell Item 자체가 navigationLink가 되어서는 안된다.
import SwiftUI struct MediaListView: View { @StateObject var viewModel: MediaListViewModel let appContainer: AppContainer var body: some View { NavigationView { ScrollView { LazyVStack { // Empty View NavigationLink( isActive: $viewModel.output.isNavigationShow, destination: { if let media = viewModel.output.selectedMedia { NavigationLazyView( MediaDetailView( viewModel: appContainer.mediaDetailViewModel(media) ) ) } }, label: { } ) // List Cell Item ForEach(viewModel.output.medias, id: \.id) { media in MediaListItemView(media: media) .wrapToButton { viewModel.action(.navigationTapped(media)) } } } .padding(.top, 20) .padding([.leading, .trailing], 12) } .navigationTitle("TV Search") .navigationBarTitleDisplayMode(.inline) .searchable(text: $viewModel.input.searchMediaSub.value) } } }
해결방법은 간단하다. List외부에 Empty View Label을 가지는 NavigationLink를 만들고, 해당 NavigationLink를 통해서 화면전환을 하면된다.
코드가 변경된 부분을 보자. 기존에는 List의 Item 자체가 NavigationLink였지만 단순히 Button 기능을 하는 버튼으로 변경된 것을 확인할 수 있다.
import Combine import Foundation final class MediaListViewModel: ViewModelType { private let searchMediaUsecase: SearchMediaUseCase struct Input { var searchMediaSub = BindingSubject<String>(value: "") fileprivate let searchButtonSub = PassthroughSubject<Void, Never>() fileprivate let navigationSub = PassthroughSubject<Media, Never>() } enum Action { case searchButtonTapped case navigationTapped(Media) } func action(_ action: Action) { switch action { case .searchButtonTapped: input.searchButtonSub.send() case .navigationTapped(let media): input.navigationSub.send(media) } } struct Output { var medias: [Media] = [] var isNavigationShow: Bool = false var selectedMedia: Media? } var input = Input() @Published var output = Output() var cancellables = Set<AnyCancellable>() init(searchMediaUsecase: SearchMediaUseCase) { self.searchMediaUsecase = searchMediaUsecase transform() } func transform() { input.searchMediaSub.subject .debounce(for: 0.5, scheduler: RunLoop.main) .flatMap { [weak self] search -> AnyPublisher<MediaPage, Never> in guard let self = self else { return Just(MediaPage(page: -1, totalPages: -1, medias: [])).eraseToAnyPublisher() } return self.searchMediaUsecase.tvExcute(query: search, page: 1) .replaceError(with: MediaPage(page: -1, totalPages: -1, medias: [])) .eraseToAnyPublisher() } .map { $0.medias } .sink(receiveValue: { [weak self] in guard let self = self else { return } self.output.medias = $0 }) .store(in: &cancellables) input.navigationSub .sink(receiveValue: { [weak self] in guard let self = self else { return } self.output.selectedMedia = $0 self.output.isNavigationShow = true }) .store(in: &cancellables) } }
뷰모델의 프레젠테이션 로직을 확인해보면, 사용자가 DetailView를 보기 위해서 버튼을 누르면 해당 버튼을 그릴 때 사용되었던 Media를 subject를 통해 넘겨주고 navigationLink의 isActive를 변경한다.
문제가 해결된것을 확인할 수 있다.
전체 프로젝트는 여기서 확인할 수 있습니다
The text was updated successfully, but these errors were encountered:
Brandnew-one
No branches or pull requests
LazyVStack - NavigationLink
LazyVstack안에 NavigationLink가 있는 경우 발생한 버그에 대해 정리해보고자 한다.
현재 사용자가 입력한 결과값에 따라 Media List를 받아오고 각 Cell을 클릭하면 DetailView로 Push 되고 있는 형태의 앱이다.
SwiftUI의 NavigationLink는 사용자가 push하기 이전뷰에서도 push될 뷰를 미리 load하고 있는 문제점(?)이 있는데 이를 방지하고자 push되는 시점에서 뷰가 그려질 수 있도록 NavigaitonLazyView를 사용하고 있다.
하지만 위와 같이 코드를 작성하고, 기존에 리스트에 있는 cell을 클릭하는 시점에 사용자가 키보드를 통해 다른 값을 입력하면 자동으로 navigation pop이 되는 문제점이 있다.
입력값이 변화함에 따라서 기존의 list가 가지고 있던 cell이 사라지면서 navigationLink도 사라져 버린것이다!
이런 문제를 어떻게 해결할 수 있을까?
이런 문제가 발생하는 이유는 list의 cell이 navigationLink라는 View로 만들어졌기 때문이다. 따라서 문제를 해결하기 위해서 List의 cell Item 자체가 navigationLink가 되어서는 안된다.
해결방법은 간단하다. List외부에 Empty View Label을 가지는 NavigationLink를 만들고, 해당 NavigationLink를 통해서 화면전환을 하면된다.
코드가 변경된 부분을 보자. 기존에는 List의 Item 자체가 NavigationLink였지만 단순히 Button 기능을 하는 버튼으로 변경된 것을 확인할 수 있다.
뷰모델의 프레젠테이션 로직을 확인해보면, 사용자가 DetailView를 보기 위해서 버튼을 누르면 해당 버튼을 그릴 때 사용되었던 Media를 subject를 통해 넘겨주고 navigationLink의 isActive를 변경한다.
문제가 해결된것을 확인할 수 있다.
전체 프로젝트는 여기서 확인할 수 있습니다
The text was updated successfully, but these errors were encountered: