Ferrite: Fix iOS 14 onAppear bug

onAppear does not fire properly on iOS 14 due to a longstanding
bug in SwiftUI. Add a UIKit onAppear hook for listening to these
events and implement inside the backport namespace.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2023-02-08 14:40:04 -05:00
parent 4512318e8f
commit 9ff7f5a7d5
23 changed files with 126 additions and 32 deletions

View file

@ -46,8 +46,8 @@
0C50055B2992BA6A0064606A /* PluginTag+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5005592992BA6A0064606A /* PluginTag+CoreDataProperties.swift */; };
0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */; };
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */; };
0C572D4C2993FC2A003EEC05 /* OnAppearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4B2993FC2A003EEC05 /* OnAppearHandler.swift */; };
0C572D4E299403B7003EEC05 /* DidAppearModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4D299403B7003EEC05 /* DidAppearModifier.swift */; };
0C572D4C2993FC2A003EEC05 /* ViewDidAppearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */; };
0C572D4E299403B7003EEC05 /* ViewDidAppearModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4D299403B7003EEC05 /* ViewDidAppearModifier.swift */; };
0C57D4CC289032ED008534E8 /* SearchResultInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultInfoView.swift */; };
0C5FCB05296744F300849E87 /* AllDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5FCB04296744F300849E87 /* AllDebridCloudView.swift */; };
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
@ -169,8 +169,8 @@
0C5005592992BA6A0064606A /* PluginTag+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginTag+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataClass.swift"; sourceTree = "<group>"; };
0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C572D4B2993FC2A003EEC05 /* OnAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnAppearHandler.swift; sourceTree = "<group>"; };
0C572D4D299403B7003EEC05 /* DidAppearModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DidAppearModifier.swift; sourceTree = "<group>"; };
0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearHandler.swift; sourceTree = "<group>"; };
0C572D4D299403B7003EEC05 /* ViewDidAppearModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearModifier.swift; sourceTree = "<group>"; };
0C57D4CB289032ED008534E8 /* SearchResultInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultInfoView.swift; sourceTree = "<group>"; };
0C5FCB04296744F300849E87 /* AllDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDebridCloudView.swift; sourceTree = "<group>"; };
0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubWrapper.swift; sourceTree = "<group>"; };
@ -380,7 +380,7 @@
0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */,
0CBAB83528D12ED500AC903E /* DisableInteraction.swift */,
0CB6516428C5A5D700DCA721 /* InlinedList.swift */,
0C572D4D299403B7003EEC05 /* DidAppearModifier.swift */,
0C572D4D299403B7003EEC05 /* ViewDidAppearModifier.swift */,
);
path = Modifiers;
sourceTree = "<group>";
@ -531,7 +531,7 @@
isa = PBXGroup;
children = (
0CA148CE288903F000DE2211 /* WebView.swift */,
0C572D4B2993FC2A003EEC05 /* OnAppearHandler.swift */,
0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */,
);
path = RepresentableViews;
sourceTree = "<group>";
@ -745,7 +745,7 @@
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */,
0CC389532970AD900066D06F /* Action+CoreDataClass.swift in Sources */,
0C03EB72296F619900162E9A /* PluginList+CoreDataProperties.swift in Sources */,
0C572D4C2993FC2A003EEC05 /* OnAppearHandler.swift in Sources */,
0C572D4C2993FC2A003EEC05 /* ViewDidAppearHandler.swift in Sources */,
0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */,
0C44E2AF28D52E8A007711AE /* BackupsView.swift in Sources */,
0CA148E1288903F000DE2211 /* Collection.swift in Sources */,
@ -795,7 +795,7 @@
0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */,
0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */,
0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */,
0C572D4E299403B7003EEC05 /* DidAppearModifier.swift in Sources */,
0C572D4E299403B7003EEC05 /* ViewDidAppearModifier.swift in Sources */,
0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */,
0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */,
0CBAB83628D12ED500AC903E /* DisableInteraction.swift in Sources */,

View file

@ -56,4 +56,8 @@ extension View {
func inlinedList() -> some View {
modifier(InlinedList())
}
func viewDidAppear(_ callback: @escaping () -> Void) -> some View {
modifier(ViewDidAppearModifier(callback: callback))
}
}

View file

@ -21,7 +21,7 @@ struct FerriteApp: App {
var body: some Scene {
WindowGroup {
MainView()
.onAppear {
.backport.onAppear {
scrapingModel.toastModel = toastModel
debridManager.toastModel = toastModel
pluginManager.toastModel = toastModel

View file

@ -7,8 +7,9 @@
import Foundation
// Version is optional until v1 is phased out
public struct Backup: Codable {
let version: Int
let version: Int?
var bookmarks: [BookmarkJson]?
var history: [HistoryJson]?
var sourceNames: [String]?

View file

@ -161,8 +161,10 @@ public class BackupManager: ObservableObject {
}
}
if let storedLists = backup.sourceLists, (backup.version == 1) {
// Only present in v1 backups
let version = backup.version ?? -1
if let storedLists = backup.sourceLists, version < 2 {
// Only present in v1 or no version backups
for list in storedLists {
try await pluginManager.addPluginList(list.urlString, existingPluginList: nil)
}

View file

@ -81,7 +81,11 @@ public class PluginManager: ObservableObject {
}
}
} catch {
toastModel?.updateToastDescription("Plugin fetch error: \(error)")
let error = error as NSError
if error.code != -999 {
toastModel?.updateToastDescription("Plugin fetch error: \(error)")
}
print("Plugin fetch error: \(error)")
}
}

View file

@ -20,7 +20,12 @@ extension View {
}
extension Backport where Content: View {
@ViewBuilder func alert(isPresented: Binding<Bool>, title: String, message: String?, buttons: [AlertButton] = []) -> some View {
@ViewBuilder func alert(
isPresented: Binding<Bool>,
title: String,
message: String?,
buttons: [AlertButton] = []
) -> some View {
if #available(iOS 15, *) {
content
.alert(
@ -63,7 +68,11 @@ extension Backport where Content: View {
}
}
@ViewBuilder func confirmationDialog(isPresented: Binding<Bool>, title: String, message: String?, buttons: [AlertButton]) -> some View {
@ViewBuilder func confirmationDialog(
isPresented: Binding<Bool>,
title: String, message: String?,
buttons: [AlertButton]
) -> some View {
if #available(iOS 15, *) {
content
.confirmationDialog(
@ -100,4 +109,18 @@ extension Backport where Content: View {
.accentColor(color)
}
}
@ViewBuilder func onAppear(callback: @escaping () -> Void) -> some View {
if #available(iOS 15, *) {
content
.onAppear {
callback()
}
} else {
content
.viewDidAppear {
callback()
}
}
}
}

View file

@ -25,7 +25,7 @@ struct IndeterminateProgressView: View {
.offset(x: -reader.size.width * 0.6, y: 0)
.offset(x: reader.size.width * 1.2 * self.offset, y: 0)
.animation(.default.repeatForever().speed(0.5), value: self.offset)
.onAppear {
.backport.onAppear {
withAnimation {
self.offset = 1
}

View file

@ -0,0 +1,17 @@
//
// ViewDidAppearModifier.swift
// Ferrite
//
// Created by Brian Dashore on 2/8/23.
//
import SwiftUI
struct ViewDidAppearModifier: ViewModifier {
let callback: () -> Void
func body(content: Content) -> some View {
content
.background(ViewDidAppearHandler(callback: callback))
}
}

View file

@ -54,7 +54,7 @@ struct BookmarksView: View {
}
.inlinedList()
.listStyle(.insetGrouped)
.onAppear {
.backport.onAppear {
if debridManager.enabledDebrids.count > 0 {
viewTask = Task {
let magnets = bookmarks.compactMap {
@ -72,7 +72,7 @@ struct BookmarksView: View {
viewTask?.cancel()
}
}
.onAppear {
.backport.onAppear {
applyPredicate()
}
.onChange(of: searchText) { _ in

View file

@ -80,7 +80,7 @@ struct AllDebridCloudView: View {
}
}
}
.onAppear {
.backport.onAppear {
viewTask = Task {
await debridManager.fetchAdCloud()
}

View file

@ -55,7 +55,7 @@ struct PremiumizeCloudView: View {
}
}
}
.onAppear {
.backport.onAppear {
viewTask = Task {
await debridManager.fetchPmCloud()
}

View file

@ -117,7 +117,7 @@ struct RealDebridCloudView: View {
}
}
}
.onAppear {
.backport.onAppear {
viewTask = Task {
await debridManager.fetchRdCloud()
}

View file

@ -27,7 +27,7 @@ struct HistoryView: View {
}
.listStyle(.insetGrouped)
}
.onAppear {
.backport.onAppear {
applyPredicate()
}
.onChange(of: searchText) { _ in

View file

@ -64,7 +64,7 @@ struct PluginListView<P: Plugin, PJ: PluginJson>: View {
.environmentObject(navModel)
}
}
.onAppear {
.backport.onAppear {
filteredAvailablePlugins = pluginManager.fetchFilteredPlugins(installedPlugins: installedPlugins, searchText: searchText)
filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins(installedPlugins: installedPlugins, searchText: searchText)
}

View file

@ -97,7 +97,7 @@ struct SourceSettingsBaseUrlView: View {
}
})
.keyboardType(.URL)
.onAppear {
.backport.onAppear {
tempBaseUrl = selectedSource.baseUrl ?? ""
}
}
@ -127,7 +127,7 @@ struct SourceSettingsApiView: View {
}
})
.autocapitalization(.none)
.onAppear {
.backport.onAppear {
tempClientId = clientId.value ?? ""
}
}
@ -140,7 +140,7 @@ struct SourceSettingsApiView: View {
}
})
.autocapitalization(.none)
.onAppear {
.backport.onAppear {
tempClientSecret = clientSecret.value ?? ""
}
}

View file

@ -138,7 +138,7 @@ struct SearchResultButtonView: View {
existingBookmark = nil
}
}
.onAppear {
.backport.onAppear {
// Only run a exists request if a bookmark isn't passed to the view
if existingBookmark == nil, !runOnce {
let bookmarkRequest = Bookmark.fetchRequest()

View file

@ -48,7 +48,7 @@ struct BackupsView: View {
.listStyle(.insetGrouped)
}
}
.onAppear {
.backport.onAppear {
backupManager.backupUrls = FileManager.default.appDirectory
.appendingPathComponent("Backups", isDirectory: true).contentsByDateAdded
}

View file

@ -32,7 +32,7 @@ struct PluginListEditorView: View {
.autocapitalization(.none)
.conditionalId(sourceUrlSet)
}
.onAppear {
.backport.onAppear {
pluginListUrl = selectedPluginList?.urlString ?? ""
sourceUrlSet = true
}

View file

@ -30,7 +30,7 @@ struct SettingsAppVersionView: View {
.listStyle(.insetGrouped)
}
}
.onAppear {
.backport.onAppear {
viewTask = Task {
do {
if let fetchedReleases = try await Github().fetchReleases() {

View file

@ -72,7 +72,7 @@ struct MainView: View {
}
}
}
.onAppear {
.backport.onAppear {
if autoUpdateNotifs {
viewTask = Task {
do {

View file

@ -75,7 +75,7 @@ struct PluginsView: View {
ProgressView()
}
}
.onAppear {
.backport.onAppear {
viewTask = Task {
await pluginManager.fetchPluginsFromUrl()
checkedForPlugins = true

View file

@ -0,0 +1,43 @@
//
// ViewDidAppearHandler.swift
// Ferrite
//
// Created by Brian Dashore on 2/8/23.
//
// UIKit onAppear hook to fix onAppear behavior in iOS 14
//
import SwiftUI
struct ViewDidAppearHandler: UIViewControllerRepresentable {
let callback: () -> Void
class Coordinator: UIViewController {
let callback: () -> Void
init(callback: @escaping () -> Void) {
self.callback = callback
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
callback()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(callback: callback)
}
func makeUIViewController(context: Context) -> UIViewController {
context.coordinator
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}