You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was developing a simple podcast application and encountered this issue when closing the player screen.
Dismissing the player screen (presented as a sheet) back to its original view results in the error: 'A parent reducer set destination state to nil.'
Would this be a bug, or an issue in my implementation??
An "ifLet" at "podcast_ios/PodcastDetailsView.swift:76" received a presentation action when the destination state was absent. …
This is generally considered an application logic error, and can happen for a few reasons:
• A parent reducer set destination state to "nil" before this reducer ran. This reducer must run before any other reducer sets destination state to "nil". This ensures that destination reducers can handle their actions while their state is still present.
• This action was sent to the store while destination state was "nil". Make sure that actions for this reducer can only be sent from a store when state is present, or from effects that start from this reducer. In SwiftUI applications, use a Composable Architecture view modifier like "sheet(store:…)".
import SwiftUI
import ComposableArchitecture
import AVFoundation
import SliderControl
import Combine
@Reducer
struct PlayerFeature {
@ObservableState
struct State: Equatable {
var player: AVPlayer?
var isPlaying: PlaybackState = .paused
var currentTime: Double = 0
var totalTime: Double = 100
var audioURL: URL?
var episode: Episode
init(episode: Episode) {
self.episode = episode
}
}
enum Action: Equatable {
case handlePlayAction
case onCurrentTimeChange(Double)
case onTotalTimeChange(Double)
case updateIsPlaying(PlaybackState)
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .handlePlayAction:
return .run { @MainActor [episode = state.episode] _ in
AudioPlayer.shared.play(item: episode, action: .playNow)
}
case .onCurrentTimeChange(let currentTime):
state.currentTime = currentTime
return .none
case .onTotalTimeChange(let totalTime):
state.totalTime = totalTime
return .none
case .updateIsPlaying(let isPlaying):
state.isPlaying = isPlaying
return .none
}
}
}
}
import SwiftUI
import ComposableArchitecture
import FeedKit
@Reducer
struct PodcastDetailsFeature {
@ObservableState
struct State: Equatable {
let podcast: Podcast
var episodes: IdentifiedArrayOf<Episode>?
var isLoading: Bool = false
@Presents var playEpisode: PlayerFeature.State?
var episodeURL: URL?
}
enum Action: Equatable {
case fetchEpisode
case cellTapped(Episode)
case playEpisode(PresentationAction<PlayerFeature.Action>)
case episodeResponse(IdentifiedArrayOf<Episode>?)
case onDisappear
}
private func parseFeed(url: URL?) async throws -> IdentifiedArrayOf<Episode> {
return try await withCheckedThrowingContinuation { continuation in
guard let url else { return }
let parser = FeedParser(URL: url)
parser.parseAsync { result in
switch result {
case let .success(feed):
guard let rssFeed = feed.rssFeed else {
continuation.resume(returning: [])
return
}
let episodes = rssFeed.toEpisodes()
continuation.resume(returning: episodes)
case let .failure(parserError):
continuation.resume(throwing: parserError)
}
}
}
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .fetchEpisode:
state.isLoading = true
return .run { [url = state.podcast.feedURL] send in
try await send(
.episodeResponse(
self.parseFeed(url: url)
)
)
}
case .episodeResponse(let response):
state.isLoading = false
state.episodes = response
return .none
case .cellTapped(let episode):
state.playEpisode = PlayerFeature.State(episode: episode)
return .none
case .playEpisode:
return .none
case .onDisappear:
state.episodes = nil
return .none
}
}
.ifLet(\.$playEpisode, action: \.playEpisode) {
PlayerFeature()
}
}
}
struct PodcastDetailsView: View {
@State var store: StoreOf<PodcastDetailsFeature>
var body: some View {
ZStack(alignment: .top) {
ScrollView {
// TODO: Pagination
Section(content: {
LazyVStack(spacing: 24) {
if (store.episodes) != nil {
ForEach((store.episodes!), id: \.self) { response in
ListEpisodeViewCell(episode: response)
.shadow(color: .black.opacity(0.2), radius: 10, x: 5, y: 5)
.onTapGesture {
store.send(.cellTapped(response))
}
}
}
}
}, header: {
ListViewHero(imageURL: store.podcast.image ?? URL(string: "")!)
.frame(width: 380, height: 380)
.padding(.bottom, 20)
})
.padding(.horizontal, 16)
}
.blur(
radius: store.isLoading ? 5 : 0
)
if store.isLoading {
ProgressView("Please wait")
.progressViewStyle(CircularProgressViewStyle(tint: .blue))
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.onAppear {
store.send(.fetchEpisode)
}
.sheet(
store: self.store.scope(
state: \.$playEpisode,
action: \.playEpisode
)
) { store in
NavigationStack {
PlayerView(store: store)
.navigationTitle(store.episode.title)
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I was developing a simple podcast application and encountered this issue when closing the player screen.
Dismissing the player screen (presented as a sheet) back to its original view results in the error: 'A parent reducer set destination state to nil.'
Would this be a bug, or an issue in my implementation??
An "ifLet" at "podcast_ios/PodcastDetailsView.swift:76" received a presentation action when the destination state was absent. …
Action:
PodcastDetailsFeature.Action.playEpisode(.presented(.onTotalTimeChange))
This is generally considered an application logic error, and can happen for a few reasons:
• A parent reducer set destination state to "nil" before this reducer ran. This reducer must run before any other reducer sets destination state to "nil". This ensures that destination reducers can handle their actions while their state is still present.
• This action was sent to the store while destination state was "nil". Make sure that actions for this reducer can only be sent from a store when state is present, or from effects that start from this reducer. In SwiftUI applications, use a Composable Architecture view modifier like "sheet(store:…)".
Beta Was this translation helpful? Give feedback.
All reactions