From 9ff7f5a7d51f1f990f4d1a9e6ea16232a7f64090 Mon Sep 17 00:00:00 2001 From: kingbri Date: Wed, 8 Feb 2023 14:40:04 -0500 Subject: [PATCH] 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 --- Ferrite.xcodeproj/project.pbxproj | 16 +++---- Ferrite/Extensions/View.swift | 4 ++ Ferrite/FerriteApp.swift | 2 +- Ferrite/Models/BackupModels.swift | 3 +- Ferrite/ViewModels/BackupManager.swift | 6 ++- Ferrite/ViewModels/PluginManager.swift | 6 ++- Ferrite/Views/CommonViews/Backport.swift | 27 +++++++++++- .../IndeterminateProgressView.swift | 2 +- .../Modifiers/ViewDidAppearModifier.swift | 17 ++++++++ .../Library/BookmarksView.swift | 4 +- .../Library/Cloud/AllDebridCloudView.swift | 2 +- .../Library/Cloud/PremiumizeCloudView.swift | 2 +- .../Library/Cloud/RealDebridCloudView.swift | 2 +- .../ComponentViews/Library/HistoryView.swift | 2 +- .../Plugin/PluginListView.swift | 2 +- .../Plugin/Source/SourceSettingsView.swift | 6 +-- .../SearchResult/SearchResultButtonView.swift | 2 +- .../ComponentViews/Settings/BackupsView.swift | 2 +- .../Settings/PluginListEditorView.swift | 2 +- .../Settings/SettingsAppVersionView.swift | 2 +- Ferrite/Views/MainView.swift | 2 +- Ferrite/Views/PluginsView.swift | 2 +- .../ViewDidAppearHandler.swift | 43 +++++++++++++++++++ 23 files changed, 126 insertions(+), 32 deletions(-) create mode 100644 Ferrite/Views/CommonViews/Modifiers/ViewDidAppearModifier.swift create mode 100644 Ferrite/Views/RepresentableViews/ViewDidAppearHandler.swift diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index a252eac..ca19997 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -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 = ""; }; 0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataClass.swift"; sourceTree = ""; }; 0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataProperties.swift"; sourceTree = ""; }; - 0C572D4B2993FC2A003EEC05 /* OnAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnAppearHandler.swift; sourceTree = ""; }; - 0C572D4D299403B7003EEC05 /* DidAppearModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DidAppearModifier.swift; sourceTree = ""; }; + 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearHandler.swift; sourceTree = ""; }; + 0C572D4D299403B7003EEC05 /* ViewDidAppearModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearModifier.swift; sourceTree = ""; }; 0C57D4CB289032ED008534E8 /* SearchResultInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultInfoView.swift; sourceTree = ""; }; 0C5FCB04296744F300849E87 /* AllDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDebridCloudView.swift; sourceTree = ""; }; 0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubWrapper.swift; sourceTree = ""; }; @@ -380,7 +380,7 @@ 0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */, 0CBAB83528D12ED500AC903E /* DisableInteraction.swift */, 0CB6516428C5A5D700DCA721 /* InlinedList.swift */, - 0C572D4D299403B7003EEC05 /* DidAppearModifier.swift */, + 0C572D4D299403B7003EEC05 /* ViewDidAppearModifier.swift */, ); path = Modifiers; sourceTree = ""; @@ -531,7 +531,7 @@ isa = PBXGroup; children = ( 0CA148CE288903F000DE2211 /* WebView.swift */, - 0C572D4B2993FC2A003EEC05 /* OnAppearHandler.swift */, + 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */, ); path = RepresentableViews; sourceTree = ""; @@ -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 */, diff --git a/Ferrite/Extensions/View.swift b/Ferrite/Extensions/View.swift index 032567f..b7910aa 100644 --- a/Ferrite/Extensions/View.swift +++ b/Ferrite/Extensions/View.swift @@ -56,4 +56,8 @@ extension View { func inlinedList() -> some View { modifier(InlinedList()) } + + func viewDidAppear(_ callback: @escaping () -> Void) -> some View { + modifier(ViewDidAppearModifier(callback: callback)) + } } diff --git a/Ferrite/FerriteApp.swift b/Ferrite/FerriteApp.swift index 8f4657d..7e5fcc8 100644 --- a/Ferrite/FerriteApp.swift +++ b/Ferrite/FerriteApp.swift @@ -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 diff --git a/Ferrite/Models/BackupModels.swift b/Ferrite/Models/BackupModels.swift index c1a7b24..4a5e2ce 100644 --- a/Ferrite/Models/BackupModels.swift +++ b/Ferrite/Models/BackupModels.swift @@ -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]? diff --git a/Ferrite/ViewModels/BackupManager.swift b/Ferrite/ViewModels/BackupManager.swift index 67b40a5..70fd27a 100644 --- a/Ferrite/ViewModels/BackupManager.swift +++ b/Ferrite/ViewModels/BackupManager.swift @@ -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) } diff --git a/Ferrite/ViewModels/PluginManager.swift b/Ferrite/ViewModels/PluginManager.swift index 4399850..4ab0c5f 100644 --- a/Ferrite/ViewModels/PluginManager.swift +++ b/Ferrite/ViewModels/PluginManager.swift @@ -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)") } } diff --git a/Ferrite/Views/CommonViews/Backport.swift b/Ferrite/Views/CommonViews/Backport.swift index ade49c0..139897b 100644 --- a/Ferrite/Views/CommonViews/Backport.swift +++ b/Ferrite/Views/CommonViews/Backport.swift @@ -20,7 +20,12 @@ extension View { } extension Backport where Content: View { - @ViewBuilder func alert(isPresented: Binding, title: String, message: String?, buttons: [AlertButton] = []) -> some View { + @ViewBuilder func alert( + isPresented: Binding, + 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, title: String, message: String?, buttons: [AlertButton]) -> some View { + @ViewBuilder func confirmationDialog( + isPresented: Binding, + 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() + } + } + } } diff --git a/Ferrite/Views/CommonViews/IndeterminateProgressView.swift b/Ferrite/Views/CommonViews/IndeterminateProgressView.swift index c9f6e33..2ce666f 100644 --- a/Ferrite/Views/CommonViews/IndeterminateProgressView.swift +++ b/Ferrite/Views/CommonViews/IndeterminateProgressView.swift @@ -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 } diff --git a/Ferrite/Views/CommonViews/Modifiers/ViewDidAppearModifier.swift b/Ferrite/Views/CommonViews/Modifiers/ViewDidAppearModifier.swift new file mode 100644 index 0000000..80524c8 --- /dev/null +++ b/Ferrite/Views/CommonViews/Modifiers/ViewDidAppearModifier.swift @@ -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)) + } +} diff --git a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift index 2172101..fdf8690 100644 --- a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift +++ b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift @@ -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 diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift index 12ac5fe..d6d6b77 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift @@ -80,7 +80,7 @@ struct AllDebridCloudView: View { } } } - .onAppear { + .backport.onAppear { viewTask = Task { await debridManager.fetchAdCloud() } diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift index 432d988..8217ea7 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift @@ -55,7 +55,7 @@ struct PremiumizeCloudView: View { } } } - .onAppear { + .backport.onAppear { viewTask = Task { await debridManager.fetchPmCloud() } diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift index 06cc77d..d826375 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift @@ -117,7 +117,7 @@ struct RealDebridCloudView: View { } } } - .onAppear { + .backport.onAppear { viewTask = Task { await debridManager.fetchRdCloud() } diff --git a/Ferrite/Views/ComponentViews/Library/HistoryView.swift b/Ferrite/Views/ComponentViews/Library/HistoryView.swift index 1e7b4cc..9567dcf 100644 --- a/Ferrite/Views/ComponentViews/Library/HistoryView.swift +++ b/Ferrite/Views/ComponentViews/Library/HistoryView.swift @@ -27,7 +27,7 @@ struct HistoryView: View { } .listStyle(.insetGrouped) } - .onAppear { + .backport.onAppear { applyPredicate() } .onChange(of: searchText) { _ in diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift index bd55e0e..ad515b8 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift @@ -64,7 +64,7 @@ struct PluginListView: View { .environmentObject(navModel) } } - .onAppear { + .backport.onAppear { filteredAvailablePlugins = pluginManager.fetchFilteredPlugins(installedPlugins: installedPlugins, searchText: searchText) filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins(installedPlugins: installedPlugins, searchText: searchText) } diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift index 7087a49..b882874 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift @@ -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 ?? "" } } diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift index 5933bfd..5a49bb7 100644 --- a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift @@ -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() diff --git a/Ferrite/Views/ComponentViews/Settings/BackupsView.swift b/Ferrite/Views/ComponentViews/Settings/BackupsView.swift index 2c5b416..8142845 100644 --- a/Ferrite/Views/ComponentViews/Settings/BackupsView.swift +++ b/Ferrite/Views/ComponentViews/Settings/BackupsView.swift @@ -48,7 +48,7 @@ struct BackupsView: View { .listStyle(.insetGrouped) } } - .onAppear { + .backport.onAppear { backupManager.backupUrls = FileManager.default.appDirectory .appendingPathComponent("Backups", isDirectory: true).contentsByDateAdded } diff --git a/Ferrite/Views/ComponentViews/Settings/PluginListEditorView.swift b/Ferrite/Views/ComponentViews/Settings/PluginListEditorView.swift index 67b8988..99c5bcf 100644 --- a/Ferrite/Views/ComponentViews/Settings/PluginListEditorView.swift +++ b/Ferrite/Views/ComponentViews/Settings/PluginListEditorView.swift @@ -32,7 +32,7 @@ struct PluginListEditorView: View { .autocapitalization(.none) .conditionalId(sourceUrlSet) } - .onAppear { + .backport.onAppear { pluginListUrl = selectedPluginList?.urlString ?? "" sourceUrlSet = true } diff --git a/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift b/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift index d340ccb..69b05f8 100644 --- a/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift +++ b/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift @@ -30,7 +30,7 @@ struct SettingsAppVersionView: View { .listStyle(.insetGrouped) } } - .onAppear { + .backport.onAppear { viewTask = Task { do { if let fetchedReleases = try await Github().fetchReleases() { diff --git a/Ferrite/Views/MainView.swift b/Ferrite/Views/MainView.swift index a3068c9..003f75b 100644 --- a/Ferrite/Views/MainView.swift +++ b/Ferrite/Views/MainView.swift @@ -72,7 +72,7 @@ struct MainView: View { } } } - .onAppear { + .backport.onAppear { if autoUpdateNotifs { viewTask = Task { do { diff --git a/Ferrite/Views/PluginsView.swift b/Ferrite/Views/PluginsView.swift index 3b7bbba..20f76d0 100644 --- a/Ferrite/Views/PluginsView.swift +++ b/Ferrite/Views/PluginsView.swift @@ -75,7 +75,7 @@ struct PluginsView: View { ProgressView() } } - .onAppear { + .backport.onAppear { viewTask = Task { await pluginManager.fetchPluginsFromUrl() checkedForPlugins = true diff --git a/Ferrite/Views/RepresentableViews/ViewDidAppearHandler.swift b/Ferrite/Views/RepresentableViews/ViewDidAppearHandler.swift new file mode 100644 index 0000000..c643486 --- /dev/null +++ b/Ferrite/Views/RepresentableViews/ViewDidAppearHandler.swift @@ -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) {} +}