From b91dc283b6609fdc445e36dc84de69d8ac647dc7 Mon Sep 17 00:00:00 2001
From: commiekong <30882689+dfsm@users.noreply.github.com>
Date: Sat, 29 Nov 2025 11:54:28 +1100
Subject: [PATCH 1/2] Initial commit for migration to SwiftUI lifecycle. No
different features or changes, retain original UIKit navigation.
---
App/Main/AppDelegate.swift | 116 ++++++----
App/Main/AwfulApp.swift | 50 +++++
App/Main/LaunchScreen.storyboard | 62 ------
.../RootViewControllerRepresentable.swift | 30 +++
App/Main/main.swift | 9 -
App/Resources/Info.plist | 9 +-
App/View Controllers/Login.storyboard | 207 ------------------
.../LoginViewController.swift | 167 +++++++++++++-
.../RootTabBarController.storyboard | 27 ---
.../RootTabBarController.swift | 35 +--
Awful.xcodeproj/project.pbxproj | 26 +--
11 files changed, 328 insertions(+), 410 deletions(-)
create mode 100644 App/Main/AwfulApp.swift
delete mode 100644 App/Main/LaunchScreen.storyboard
create mode 100644 App/Main/RootViewControllerRepresentable.swift
delete mode 100644 App/Main/main.swift
delete mode 100644 App/View Controllers/Login.storyboard
delete mode 100644 App/View Controllers/RootTabBarController.storyboard
diff --git a/App/Main/AppDelegate.swift b/App/Main/AppDelegate.swift
index f0fe31c7f..531fcba0d 100644
--- a/App/Main/AppDelegate.swift
+++ b/App/Main/AppDelegate.swift
@@ -57,23 +57,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
return URLCache(memoryCapacity: megabytes(5), diskCapacity: megabytes(50), diskPath: nil)
#endif
}()
-
- window = UIWindow(frame: UIScreen.main.bounds)
- window?.tintColor = Theme.defaultTheme()["tintColor"]
-
- if ForumsClient.shared.isLoggedIn {
- setRootViewController(rootViewControllerStack.rootViewController, animated: false, completion: nil)
- } else {
- setRootViewController(loginViewController.enclosingNavigationController, animated: false, completion: nil)
- }
-
- openCopiedURLController = OpenCopiedURLController(window: window!, router: {
- [unowned self] in
- self.open(route: $0)
- })
-
- window?.makeKeyAndVisible()
-
+
return true
}
@@ -81,9 +65,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
- // Don't want to lazily create it now.
- _rootViewControllerStack?.didAppear()
-
ignoreSilentSwitchWhenPlayingEmbeddedVideo()
showPromptIfLoginCookieExpiresSoon()
@@ -144,10 +125,26 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidBecomeActive(_ application: UIApplication) {
SmilieKeyboardSetIsAwfulAppActive(true)
-
+
// Screen brightness may have changed while the app wasn't paying attention.
automaticallyUpdateDarkModeEnabledIfNecessary()
}
+
+ func applicationDidEnterBackground(_ application: UIApplication) {
+ do {
+ try managedObjectContext.save()
+ } catch {
+ logger.error("Failed to save context on background: \(error)")
+ }
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ do {
+ try managedObjectContext.save()
+ } catch {
+ logger.error("Failed to save context on termination: \(error)")
+ }
+ }
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return ForumsClient.shared.isLoggedIn
@@ -221,7 +218,16 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
urlRouter?.route(route)
}
- private func updateShortcutItems() {
+ func automaticallyUpdateDarkModeEnabledIfNecessary() {
+ guard automaticDarkTheme else { return }
+
+ let shouldDarkModeBeEnabled = (window ?? keyWindow)?.traitCollection.userInterfaceStyle == .dark
+ if shouldDarkModeBeEnabled != darkMode {
+ darkMode.toggle()
+ }
+ }
+
+ func updateShortcutItems() {
guard urlRouter != nil else {
UIApplication.shared.shortcutItems = []
return
@@ -268,7 +274,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
private var _rootViewControllerStack: RootViewControllerStack?
private var urlRouter: AwfulURLRouter?
- private var rootViewControllerStack: RootViewControllerStack {
+ var rootViewControllerStack: RootViewControllerStack {
if let stack = _rootViewControllerStack { return stack }
let stack = RootViewControllerStack(managedObjectContext: managedObjectContext)
urlRouter = AwfulURLRouter(rootViewController: stack.rootViewController, managedObjectContext: managedObjectContext)
@@ -276,33 +282,56 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
_rootViewControllerStack = stack
return stack
}
-
- private lazy var loginViewController: LoginViewController! = {
+
+ lazy var loginViewController: LoginViewController! = {
let loginVC = LoginViewController.newFromStoryboard()
loginVC.completionBlock = { [weak self] (login) in
guard let self = self else { return }
- self.setRootViewController(self.rootViewControllerStack.rootViewController, animated: true, completion: { [weak self] in
- guard let self = self else { return }
- self.rootViewControllerStack.didAppear()
- self.loginViewController = nil
- })
+ self.swapToMainViewController()
}
return loginVC
}()
+
+ func swapToMainViewController() {
+ guard let window = self.window ?? self.keyWindow else { return }
+
+ UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
+ window.rootViewController = self.rootViewControllerStack.rootViewController
+ }) { [weak self] _ in
+ self?.rootViewControllerStack.didAppear()
+ self?.loginViewController = nil
+ }
+ }
+
+ func setupOpenCopiedURLController() {
+ guard openCopiedURLController == nil else { return }
+ guard let window = self.window ?? self.keyWindow else { return }
+
+ openCopiedURLController = OpenCopiedURLController(window: window, router: { [unowned self] in
+ self.open(route: $0)
+ })
+ }
+
+ private var keyWindow: UIWindow? {
+ return UIApplication.shared.connectedScenes
+ .compactMap { $0 as? UIWindowScene }
+ .flatMap { $0.windows }
+ .first { $0.isKeyWindow }
+ }
}
private extension AppDelegate {
func setRootViewController(_ rootViewController: UIViewController, animated: Bool, completion: (() -> Void)?) {
- guard let window = window else { return }
- UIView.transition(with: window, duration: animated ? 0.3 : 0, options: .transitionCrossDissolve, animations: {
+ guard let window = window ?? keyWindow else { return }
+ UIView.transition(with: window, duration: animated ? 0.3 : 0, options: .transitionCrossDissolve, animations: {
window.rootViewController = rootViewController
}) { (completed) in
completion?()
}
}
-
+
func themeDidChange() {
- guard let window = window else { return }
+ guard let window = window ?? keyWindow else { return }
window.tintColor = Theme.defaultTheme()["tintColor"]
@@ -320,7 +349,11 @@ private extension AppDelegate {
}
private func showSnapshotDuringThemeDidChange() {
- if let window = window, let snapshot = window.snapshotView(afterScreenUpdates: false) {
+ guard let window = window ?? keyWindow else {
+ themeDidChange()
+ return
+ }
+ if let snapshot = window.snapshotView(afterScreenUpdates: false) {
window.addSubview(snapshot)
themeDidChange()
@@ -338,15 +371,6 @@ private extension AppDelegate {
}
}
- private func automaticallyUpdateDarkModeEnabledIfNecessary() {
- guard automaticDarkTheme else { return }
-
- let shouldDarkModeBeEnabled = window?.traitCollection.userInterfaceStyle == .dark
- if shouldDarkModeBeEnabled != darkMode {
- darkMode.toggle()
- }
- }
-
@objc func preferredContentSizeDidChange(_ notification: Notification) {
themeDidChange()
}
@@ -358,7 +382,7 @@ private extension AppDelegate {
else { return }
let lastPromptDate = UserDefaults.standard.object(forKey: loginCookieLastExpiryPromptDateKey) as? Date ?? .distantFuture
guard lastPromptDate.timeIntervalSinceNow < -loginCookieExpiryPromptFrequency else { return }
-
+
let alert = UIAlertController(
title: LocalizedString("session-expiry-imminent.title"),
message: String(format: LocalizedString("session-expiry-imminent.message"), DateFormatter.localizedString(from: expiryDate, dateStyle: .short, timeStyle: .none)),
@@ -366,7 +390,7 @@ private extension AppDelegate {
UserDefaults.standard.set(Date(), forKey: loginCookieLastExpiryPromptDateKey)
})]
)
- window?.rootViewController?.present(alert, animated: true, completion: nil)
+ (window ?? keyWindow)?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
diff --git a/App/Main/AwfulApp.swift b/App/Main/AwfulApp.swift
new file mode 100644
index 000000000..3689090a5
--- /dev/null
+++ b/App/Main/AwfulApp.swift
@@ -0,0 +1,50 @@
+// AwfulApp.swift
+//
+// Copyright 2025 Awful Contributors. CC BY-NC-SA 3.0 US https://github.com/Awful/Awful.app
+
+import AwfulCore
+import Smilies
+import SwiftUI
+
+@main
+struct AwfulApp: App {
+ @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
+ @SwiftUI.Environment(\.scenePhase) private var scenePhase
+
+ var body: some Scene {
+ WindowGroup {
+ RootViewControllerRepresentable()
+ .ignoresSafeArea()
+ .onOpenURL { url in handleURL(url) }
+ }
+ .onChange(of: scenePhase) { newPhase in
+ handleScenePhaseChange(newPhase)
+ }
+ }
+
+ private func handleURL(_ url: URL) {
+ guard ForumsClient.shared.isLoggedIn,
+ let route = try? AwfulRoute(url)
+ else { return }
+ appDelegate.open(route: route)
+ }
+
+ private func handleScenePhaseChange(_ newPhase: ScenePhase) {
+ switch newPhase {
+ case .active:
+ SmilieKeyboardSetIsAwfulAppActive(true)
+ appDelegate.automaticallyUpdateDarkModeEnabledIfNecessary()
+ case .inactive:
+ SmilieKeyboardSetIsAwfulAppActive(false)
+ appDelegate.updateShortcutItems()
+ case .background:
+ do {
+ try appDelegate.managedObjectContext.save()
+ } catch {
+ print("Failed to save on background: \(error)")
+ }
+ @unknown default:
+ break
+ }
+ }
+}
diff --git a/App/Main/LaunchScreen.storyboard b/App/Main/LaunchScreen.storyboard
deleted file mode 100644
index e13de12c8..000000000
--- a/App/Main/LaunchScreen.storyboard
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/App/Main/RootViewControllerRepresentable.swift b/App/Main/RootViewControllerRepresentable.swift
new file mode 100644
index 000000000..0456071a2
--- /dev/null
+++ b/App/Main/RootViewControllerRepresentable.swift
@@ -0,0 +1,30 @@
+// RootViewControllerRepresentable.swift
+//
+// Copyright 2025 Awful Contributors. CC BY-NC-SA 3.0 US https://github.com/Awful/Awful.app
+
+import AwfulCore
+import SwiftUI
+import UIKit
+
+struct RootViewControllerRepresentable: UIViewControllerRepresentable {
+ func makeUIViewController(context: Context) -> UIViewController {
+ guard let appDelegate = AppDelegate.instance else {
+ fatalError("AppDelegate not initialized")
+ }
+
+ let rootVC: UIViewController
+ if ForumsClient.shared.isLoggedIn {
+ rootVC = appDelegate.rootViewControllerStack.rootViewController
+ appDelegate.rootViewControllerStack.didAppear()
+ } else {
+ rootVC = appDelegate.loginViewController.enclosingNavigationController
+ }
+
+ appDelegate.setupOpenCopiedURLController()
+
+ return rootVC
+ }
+
+ func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
+ }
+}
diff --git a/App/Main/main.swift b/App/Main/main.swift
deleted file mode 100644
index 214570523..000000000
--- a/App/Main/main.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-// main.swift
-//
-// Copyright 2018 Awful Contributors. CC BY-NC-SA 3.0 US https://github.com/Awful/Awful.app
-
-import UIKit
-
-// Don't bother loading the app delegate when we're running tests.
-private let appDelegateClassName = NSClassFromString("XCTestCase") == nil ? NSStringFromClass(AppDelegate.self) : nil
-UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, appDelegateClassName)
diff --git a/App/Resources/Info.plist b/App/Resources/Info.plist
index 3aecf995b..c37271767 100644
--- a/App/Resources/Info.plist
+++ b/App/Resources/Info.plist
@@ -76,8 +76,13 @@
com.awfulapp.Awful.activity.listing-threads
com.awfulapp.Awful.activity.reading-message
- UILaunchStoryboardName
- LaunchScreen
+ UILaunchScreen
+
+ UIColorName
+ LaunchBackground
+ UIImageRespectsSafeAreaInsets
+
+
UIPrerenderedIcon
UIStatusBarStyle
diff --git a/App/View Controllers/Login.storyboard b/App/View Controllers/Login.storyboard
deleted file mode 100644
index cd3f27dec..000000000
--- a/App/View Controllers/Login.storyboard
+++ /dev/null
@@ -1,207 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/App/View Controllers/LoginViewController.swift b/App/View Controllers/LoginViewController.swift
index 3def7c4c0..054124208 100644
--- a/App/View Controllers/LoginViewController.swift
+++ b/App/View Controllers/LoginViewController.swift
@@ -16,14 +16,14 @@ private let termsOfServiceURL = URL(string: "https://www.somethingawful.com/foru
class LoginViewController: ViewController {
var completionBlock: ((LoginViewController) -> Void)?
-
- @IBOutlet weak var scrollView: UIScrollView!
- @IBOutlet weak var usernameTextField: UITextField!
- @IBOutlet weak var passwordTextField: UITextField!
- @IBOutlet weak var nextBarButtonItem: UIBarButtonItem!
- @IBOutlet weak var forgotPasswordButton: UIButton!
- @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
- @IBOutlet private var consentToTermsTextView: UITextView!
+
+ private(set) var scrollView: UIScrollView!
+ private(set) var usernameTextField: UITextField!
+ private(set) var passwordTextField: UITextField!
+ private(set) var nextBarButtonItem: UIBarButtonItem!
+ private(set) var forgotPasswordButton: UIButton!
+ private(set) var activityIndicator: UIActivityIndicatorView!
+ private var consentToTermsTextView: UITextView!
@FoilDefaultStorage(Settings.canSendPrivateMessages) private var canSendPrivateMessages
@FoilDefaultStorageOptional(Settings.userID) private var userID
@@ -77,13 +77,17 @@ class LoginViewController: ViewController {
}
class func newFromStoryboard() -> LoginViewController {
- return UIStoryboard(name: "Login", bundle: nil).instantiateInitialViewController() as! LoginViewController
+ return LoginViewController()
}
+ override func loadView() {
+ super.loadView()
+ setupViews()
+ }
+
override func viewDidLoad() {
super.viewDidLoad()
-
- // Can't set this in the storyboard for some reason.
+
nextBarButtonItem.isEnabled = false
consentToTermsTextView.attributedText = {
@@ -158,6 +162,147 @@ class LoginViewController: ViewController {
@IBAction func didTapForgetPassword() {
UIApplication.shared.open(lostPasswordURL)
}
+
+ private func setupViews() {
+ title = "Log In"
+
+ scrollView = UIScrollView()
+ scrollView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(scrollView)
+
+ let contentView = UIView()
+ contentView.translatesAutoresizingMaskIntoConstraints = false
+ scrollView.addSubview(contentView)
+
+ let textFieldContainer = UIView()
+ textFieldContainer.translatesAutoresizingMaskIntoConstraints = false
+ textFieldContainer.backgroundColor = .systemBackground
+ contentView.addSubview(textFieldContainer)
+
+ let usernameLabel = UILabel()
+ usernameLabel.translatesAutoresizingMaskIntoConstraints = false
+ usernameLabel.text = "Username"
+ usernameLabel.font = .preferredFont(forTextStyle: .body)
+ usernameLabel.textAlignment = .right
+ textFieldContainer.addSubview(usernameLabel)
+
+ usernameTextField = UITextField()
+ usernameTextField.translatesAutoresizingMaskIntoConstraints = false
+ usernameTextField.placeholder = "Forums Poster"
+ usernameTextField.font = .preferredFont(forTextStyle: .body)
+ usernameTextField.autocorrectionType = .no
+ usernameTextField.spellCheckingType = .no
+ usernameTextField.delegate = self
+ usernameTextField.addTarget(self, action: #selector(didChangeUsername(_:)), for: .editingChanged)
+ textFieldContainer.addSubview(usernameTextField)
+
+ let separator1 = UIView()
+ separator1.translatesAutoresizingMaskIntoConstraints = false
+ separator1.backgroundColor = UIColor(red: 0.902, green: 0.902, blue: 0.902, alpha: 1.0)
+ textFieldContainer.addSubview(separator1)
+
+ let passwordLabel = UILabel()
+ passwordLabel.translatesAutoresizingMaskIntoConstraints = false
+ passwordLabel.text = "Password"
+ passwordLabel.font = .preferredFont(forTextStyle: .body)
+ passwordLabel.textAlignment = .right
+ textFieldContainer.addSubview(passwordLabel)
+
+ passwordTextField = UITextField()
+ passwordTextField.translatesAutoresizingMaskIntoConstraints = false
+ passwordTextField.placeholder = "Required"
+ passwordTextField.font = .preferredFont(forTextStyle: .body)
+ passwordTextField.autocorrectionType = .no
+ passwordTextField.spellCheckingType = .no
+ passwordTextField.isSecureTextEntry = true
+ passwordTextField.delegate = self
+ passwordTextField.addTarget(self, action: #selector(didChangePassword(_:)), for: .editingChanged)
+ textFieldContainer.addSubview(passwordTextField)
+
+ let separator2 = UIView()
+ separator2.translatesAutoresizingMaskIntoConstraints = false
+ separator2.backgroundColor = UIColor(red: 0.902, green: 0.902, blue: 0.902, alpha: 1.0)
+ textFieldContainer.addSubview(separator2)
+
+ forgotPasswordButton = UIButton(type: .system)
+ forgotPasswordButton.translatesAutoresizingMaskIntoConstraints = false
+ forgotPasswordButton.setTitle("Forgot your password?", for: .normal)
+ forgotPasswordButton.addTarget(self, action: #selector(didTapForgetPassword), for: .touchUpInside)
+ contentView.addSubview(forgotPasswordButton)
+
+ activityIndicator = UIActivityIndicatorView(style: .medium)
+ activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+ activityIndicator.hidesWhenStopped = true
+ contentView.addSubview(activityIndicator)
+
+ consentToTermsTextView = UITextView()
+ consentToTermsTextView.translatesAutoresizingMaskIntoConstraints = false
+ consentToTermsTextView.isEditable = false
+ consentToTermsTextView.isScrollEnabled = false
+ consentToTermsTextView.backgroundColor = .clear
+ consentToTermsTextView.delegate = self
+ contentView.addSubview(consentToTermsTextView)
+
+ nextBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(didTapNext))
+ navigationItem.rightBarButtonItem = nextBarButtonItem
+
+ NSLayoutConstraint.activate([
+ scrollView.topAnchor.constraint(equalTo: view.topAnchor),
+ scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
+ contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
+ contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
+ contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
+ contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
+ contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
+
+ textFieldContainer.topAnchor.constraint(equalTo: contentView.topAnchor),
+ textFieldContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+ textFieldContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+ textFieldContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: 320),
+ textFieldContainer.widthAnchor.constraint(lessThanOrEqualToConstant: 480),
+
+ usernameLabel.leadingAnchor.constraint(equalTo: textFieldContainer.leadingAnchor, constant: 8),
+ usernameLabel.topAnchor.constraint(equalTo: textFieldContainer.topAnchor, constant: 36),
+ usernameLabel.widthAnchor.constraint(equalToConstant: 90),
+
+ usernameTextField.leadingAnchor.constraint(equalTo: usernameLabel.trailingAnchor, constant: 14),
+ usernameTextField.trailingAnchor.constraint(equalTo: textFieldContainer.trailingAnchor, constant: -8),
+ usernameTextField.firstBaselineAnchor.constraint(equalTo: usernameLabel.firstBaselineAnchor),
+
+ separator1.leadingAnchor.constraint(equalTo: usernameLabel.leadingAnchor),
+ separator1.trailingAnchor.constraint(equalTo: usernameTextField.trailingAnchor),
+ separator1.topAnchor.constraint(equalTo: usernameTextField.bottomAnchor, constant: 7),
+ separator1.heightAnchor.constraint(equalToConstant: 0.5),
+
+ passwordLabel.leadingAnchor.constraint(equalTo: textFieldContainer.leadingAnchor, constant: 8),
+ passwordLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: 16),
+ passwordLabel.widthAnchor.constraint(equalTo: usernameLabel.widthAnchor),
+
+ passwordTextField.leadingAnchor.constraint(equalTo: usernameTextField.leadingAnchor),
+ passwordTextField.trailingAnchor.constraint(equalTo: usernameTextField.trailingAnchor),
+ passwordTextField.firstBaselineAnchor.constraint(equalTo: passwordLabel.firstBaselineAnchor),
+
+ separator2.leadingAnchor.constraint(equalTo: passwordLabel.leadingAnchor),
+ separator2.trailingAnchor.constraint(equalTo: passwordTextField.trailingAnchor),
+ separator2.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 7),
+ separator2.heightAnchor.constraint(equalToConstant: 0.5),
+ separator2.bottomAnchor.constraint(equalTo: textFieldContainer.bottomAnchor),
+
+ forgotPasswordButton.topAnchor.constraint(equalTo: textFieldContainer.bottomAnchor, constant: 16),
+ forgotPasswordButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
+
+ activityIndicator.topAnchor.constraint(equalTo: forgotPasswordButton.bottomAnchor, constant: 16),
+ activityIndicator.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
+
+ consentToTermsTextView.topAnchor.constraint(equalTo: activityIndicator.bottomAnchor, constant: 16),
+ consentToTermsTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
+ consentToTermsTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
+ consentToTermsTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16)
+ ])
+ }
}
extension LoginViewController: UITextFieldDelegate {
diff --git a/App/View Controllers/RootTabBarController.storyboard b/App/View Controllers/RootTabBarController.storyboard
deleted file mode 100644
index c4e1d1c92..000000000
--- a/App/View Controllers/RootTabBarController.storyboard
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/App/View Controllers/RootTabBarController.swift b/App/View Controllers/RootTabBarController.swift
index fbb59fa07..574acbed2 100644
--- a/App/View Controllers/RootTabBarController.swift
+++ b/App/View Controllers/RootTabBarController.swift
@@ -6,22 +6,16 @@ import AwfulSettings
import AwfulTheming
import UIKit
-/// A themeable tab bar controller that fixes an iOS 11 layout problem.
+/// A themeable tab bar controller.
final class RootTabBarController: UITabBarController, UITabBarControllerDelegate, Themeable {
@FoilDefaultStorage(Settings.enableHaptics) private var enableHaptics
- /// Returns a tab bar controller whose tab bar is an instance of `TabBar_FixiOS11iPadLayout`.
static func makeWithTabBarFixedForiOS11iPadLayout() -> RootTabBarController {
- let storyboard = UIStoryboard(name: "RootTabBarController", bundle: Bundle(for: RootTabBarController.self))
- guard let tabBarController = storyboard.instantiateInitialViewController() as? RootTabBarController else {
- fatalError("initial view controller in RootTabBarController.storyboard should be a RootTabBarController")
- }
- return tabBarController
+ return RootTabBarController(nibName: nil, bundle: nil)
}
- /// Use `makeWithTabBarFixedForiOS11iPadLayout()` instead.
- private override init(nibName: String?, bundle: Bundle?) {
+ override init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
commonInit()
}
@@ -40,8 +34,8 @@ final class RootTabBarController: UITabBarController, UITabBarControllerDelegate
#endif
}
- override var tabBar: RootTabBar {
- return super.tabBar as! RootTabBar
+ private var customTabBar: RootTabBar? {
+ return super.tabBar as? RootTabBar
}
var theme: Theme {
@@ -67,7 +61,7 @@ final class RootTabBarController: UITabBarController, UITabBarControllerDelegate
tabBar.barTintColor = theme["tabBarBackgroundColor"]
tabBar.isTranslucent = theme[bool: "tabBarIsTranslucent"] ?? true
tabBar.tintColor = theme["tintColor"]
- tabBar.topBorderColor = theme["bottomBarTopBorderColor"]
+ customTabBar?.topBorderColor = theme["bottomBarTopBorderColor"]
let barAppearance = UITabBarAppearance()
if tabBar.isTranslucent {
@@ -93,20 +87,11 @@ final class RootTabBarController: UITabBarController, UITabBarControllerDelegate
}
/**
- A tab bar that fixes some issues we've come across. Some fixes are specific to Awful's particular use of the tab bar, so this may not be suitable as a general-purpose fix-it subclass.
-
- On iOS 11, `UITabBar` lays out its items with title and icon stacked horizontally whenever we're in a horizontally regular size class, and it does not do well if we constrict its width. Everything falls apart when the tab bar is in the primary view controller of a split view controller. This subclass overrides `traitCollection` and forces an always compact horizontal size class.
+ A tab bar that fixes layout issues with safe area insets.
On iOS 12, `UITabBar` has a hard time with safe area insets and can completely mess up its own layout after some combination of `hidesBottomBarOnPush` and/or full-screen modal presentation. The layout mess usually fixes itself after the pop animation that results in the tab bar appearing. This subclass overrides `sizeThatFits(_:)` and ensures that the returned height considers the safe area insets.
- Hilariously, the most reasonable way to convince a `UITabBarController` to use a custom `UITabBar` subclass is in a storyboard, so we use `RootTabBarController.storyboard`.
-
- - Seealso: `RootTabBarController.makeWithTabBarFixedForiOS11iPadLayout()`
- - Seealso: https://github.com/Awful/Awful.app/issues/357 where we were trying to puzzle this out.
- - Seealso: https://stackoverflow.com/a/45945937 which has this subclassing solution.
- - Seealso: https://github.com/bnickel/HidingTabBar which mentions that storyboard is the only reasonable way to crowbar a `UITabBar` subclass into a `UITabBarController`.
- Seealso: https://stackoverflow.com/a/53524635 which overrides `sizeThatFits(_:)` to force safe area consideration.
- - Seealso: `UITabBar+FixiOS12_1Layout.h` which addresses another UITabBar issue in iOS 12.
*/
final class RootTabBar: UITabBar {
@@ -122,12 +107,6 @@ final class RootTabBar: UITabBar {
set { topBorder.backgroundColor = newValue }
}
- override var traitCollection: UITraitCollection {
- return UITraitCollection(traitsFrom: [
- super.traitCollection,
- UITraitCollection(horizontalSizeClass: .compact)])
- }
-
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
let bottomInset = safeAreaInsets.bottom
diff --git a/Awful.xcodeproj/project.pbxproj b/Awful.xcodeproj/project.pbxproj
index b32003490..0e4f6f9c7 100644
--- a/Awful.xcodeproj/project.pbxproj
+++ b/Awful.xcodeproj/project.pbxproj
@@ -148,7 +148,6 @@
1C8F680B222B8F06007E61ED /* NamedThreadTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C8F680A222B8F06007E61ED /* NamedThreadTag.swift */; };
1C917CF81C4F21B800BBF672 /* HairlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC22AB419F972C200D5BABD /* HairlineView.swift */; };
1C9AEBC6210C3B2300C9A567 /* CloseBBcodeTagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C9AEBC5210C3B2300C9A567 /* CloseBBcodeTagTests.swift */; };
- 1C9AEBCE210C3BAF00C9A567 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C9AEBCD210C3BAF00C9A567 /* main.swift */; };
1CA3D6FC2D98A7E400D70964 /* OEmbedFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CA3D6FB2D98A7E100D70964 /* OEmbedFetcher.swift */; };
1CA45D941F2C0AD1005BEEC5 /* RenderView.js in Resources */ = {isa = PBXBuildFile; fileRef = 1CA45D931F2C0AD1005BEEC5 /* RenderView.js */; };
1CA56FEB1A009BDF009A91AE /* PotentiallyObjectionableTexts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1CA56FEA1A009BDF009A91AE /* PotentiallyObjectionableTexts.plist */; };
@@ -179,14 +178,11 @@
1CD9FB641D1A38030070C8C7 /* NigglyRefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD9FB631D1A38030070C8C7 /* NigglyRefreshView.swift */; };
1CDC53DA21FCF38F0086BD2B /* OpenCopiedURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDC53D921FCF38F0086BD2B /* OpenCopiedURLController.swift */; };
1CDC53DC220131400086BD2B /* ImgurAnonymousAPI+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDC53DB220131400086BD2B /* ImgurAnonymousAPI+Shared.swift */; };
- 1CDD0A4719B7D89B009811C4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1CDD0A4619B7D89B009811C4 /* LaunchScreen.storyboard */; };
1CDD0A4919B7EB3F009811C4 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDD0A4819B7EB3E009811C4 /* ProfileViewController.swift */; };
1CE2B76819C2372200FDC33E /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CE2B76619C2372200FDC33E /* LoginViewController.swift */; };
- 1CE2B76B19C2374C00FDC33E /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1CE2B76A19C2374C00FDC33E /* Login.storyboard */; };
1CE55A7A1A1072D900E474A6 /* ForumsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CE55A791A1072D900E474A6 /* ForumsTableViewController.swift */; };
1CEB5BFF19AB9C1700C82C30 /* InAppActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CEB5BFE19AB9C1700C82C30 /* InAppActionViewController.swift */; };
1CF186A617D48E5700B26717 /* Thread Tags in Resources */ = {isa = PBXBuildFile; fileRef = 1CF186A517D48E5700B26717 /* Thread Tags */; };
- 1CF264CA1F7811EA0059CCCA /* RootTabBarController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1CF264C91F7811EA0059CCCA /* RootTabBarController.storyboard */; };
1CF280982055EB9B00913149 /* AwfulRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF280972055EB9B00913149 /* AwfulRoute.swift */; };
1CF521752C228E76009712A7 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1CF521742C228E76009712A7 /* PrivacyInfo.xcprivacy */; };
1CF521772C228F88009712A7 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1CF521762C228F88009712A7 /* PrivacyInfo.xcprivacy */; };
@@ -204,6 +200,8 @@
2D327DD627F468CE00D21AB0 /* BookmarkColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D327DD527F468CE00D21AB0 /* BookmarkColorPicker.swift */; };
2D921269292F588100B16011 /* platinum-member.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D921268292F588100B16011 /* platinum-member.png */; };
2DAF1FE12E05D3ED006F6BC4 /* View+FontDesign.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DAF1FE02E05D3EB006F6BC4 /* View+FontDesign.swift */; };
+ 2DBE262F2EDA765700E095A6 /* RootViewControllerRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBE262E2EDA765700E095A6 /* RootViewControllerRepresentable.swift */; };
+ 2DBE26302EDA765700E095A6 /* AwfulApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBE262D2EDA765700E095A6 /* AwfulApp.swift */; };
2DD8209C25DDD9BF0015A90D /* CopyImageActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DD8209B25DDD9BF0015A90D /* CopyImageActivity.swift */; };
306F740B2D90AA01000717BC /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 306F740A2D90AA01000717BC /* KeychainAccess */; };
30E0C51D2E35C89D0030DC0A /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E0C5162E35C89D0030DC0A /* AnimatedImageView.swift */; };
@@ -450,7 +448,6 @@
1C9AEBC3210C3B2200C9A567 /* AwfulTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AwfulTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1C9AEBC5210C3B2300C9A567 /* CloseBBcodeTagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseBBcodeTagTests.swift; sourceTree = ""; };
1C9AEBC7210C3B2300C9A567 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 1C9AEBCD210C3BAF00C9A567 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
1CA3D6FB2D98A7E100D70964 /* OEmbedFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OEmbedFetcher.swift; sourceTree = ""; };
1CA45D931F2C0AD1005BEEC5 /* RenderView.js */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.javascript; path = RenderView.js; sourceTree = ""; tabWidth = 2; };
1CA56FEA1A009BDF009A91AE /* PotentiallyObjectionableTexts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = PotentiallyObjectionableTexts.plist; sourceTree = ""; };
@@ -492,15 +489,12 @@
1CDC53D921FCF38F0086BD2B /* OpenCopiedURLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCopiedURLController.swift; sourceTree = ""; };
1CDC53DB220131400086BD2B /* ImgurAnonymousAPI+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImgurAnonymousAPI+Shared.swift"; sourceTree = ""; };
1CDC9F652AE5FB19005DA08D /* test.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; name = test.yml; path = .github/workflows/test.yml; sourceTree = SOURCE_ROOT; };
- 1CDD0A4619B7D89B009811C4 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
1CDD0A4819B7EB3E009811C4 /* ProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; };
1CE2B76619C2372200FDC33E /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; };
- 1CE2B76A19C2374C00FDC33E /* Login.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Login.storyboard; sourceTree = ""; };
1CE55A791A1072D900E474A6 /* ForumsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForumsTableViewController.swift; sourceTree = ""; };
1CEB5BFE19AB9C1700C82C30 /* InAppActionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppActionViewController.swift; sourceTree = ""; };
1CF0A6C520A50FC5004C26EA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
1CF186A517D48E5700B26717 /* Thread Tags */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Thread Tags"; sourceTree = ""; };
- 1CF264C91F7811EA0059CCCA /* RootTabBarController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RootTabBarController.storyboard; sourceTree = ""; };
1CF280972055EB9B00913149 /* AwfulRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AwfulRoute.swift; sourceTree = ""; };
1CF521742C228E76009712A7 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
1CF521762C228F88009712A7 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
@@ -519,9 +513,10 @@
2D327DD527F468CE00D21AB0 /* BookmarkColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkColorPicker.swift; sourceTree = ""; };
2D921268292F588100B16011 /* platinum-member.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "platinum-member.png"; sourceTree = ""; };
2DAF1FE02E05D3EB006F6BC4 /* View+FontDesign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+FontDesign.swift"; sourceTree = ""; };
+ 2DBE262D2EDA765700E095A6 /* AwfulApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AwfulApp.swift; sourceTree = ""; };
+ 2DBE262E2EDA765700E095A6 /* RootViewControllerRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewControllerRepresentable.swift; sourceTree = ""; };
2DD8209B25DDD9BF0015A90D /* CopyImageActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyImageActivity.swift; sourceTree = ""; };
30E0C5162E35C89D0030DC0A /* AnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedImageView.swift; sourceTree = ""; };
- 30E0C5172E35C89D0030DC0A /* SmilieData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmilieData.swift; sourceTree = ""; };
30E0C5182E35C89D0030DC0A /* SmilieGridItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmilieGridItem.swift; sourceTree = ""; };
30E0C5192E35C89D0030DC0A /* SmiliePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmiliePickerView.swift; sourceTree = ""; };
30E0C51A2E35C89D0030DC0A /* SmilieSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmilieSearchViewModel.swift; sourceTree = ""; };
@@ -611,12 +606,12 @@
1190F71C13BE4EA900B9D271 /* Main */ = {
isa = PBXGroup;
children = (
+ 2DBE262D2EDA765700E095A6 /* AwfulApp.swift */,
+ 2DBE262E2EDA765700E095A6 /* RootViewControllerRepresentable.swift */,
1C2C1F131CEE90D900CD27DD /* AppDelegate.swift */,
1CBE1B1119CAAFA200510187 /* AwfulSplitViewController.swift */,
1C220E3C2B815AFC00DA92B0 /* Bundle+.swift */,
1C4506C31A2BAB3800767306 /* Handoff.swift */,
- 1CDD0A4619B7D89B009811C4 /* LaunchScreen.storyboard */,
- 1C9AEBCD210C3BAF00C9A567 /* main.swift */,
1CF521762C228F88009712A7 /* PrivacyInfo.xcprivacy */,
1C8D114619ACDAD9005D46CB /* RootViewControllerStack.swift */,
);
@@ -1036,13 +1031,11 @@
1C16FBB11CB86ACD00C88BD1 /* EmptyViewController.swift */,
1C29C3812258538A00E1217A /* Forums */,
1CBE1B0819CA050300510187 /* ImageViewController.swift */,
- 1CE2B76A19C2374C00FDC33E /* Login.storyboard */,
1CE2B76619C2372200FDC33E /* LoginViewController.swift */,
1C29C3852258547C00E1217A /* Messages */,
1C29C382225853A300E1217A /* Posts */,
1CDD0A4819B7EB3E009811C4 /* ProfileViewController.swift */,
1C29C3842258544B00E1217A /* Rap Sheet */,
- 1CF264C91F7811EA0059CCCA /* RootTabBarController.storyboard */,
1C29BD54225121F100E1217A /* RootTabBarController.swift */,
1C29C3872258551700E1217A /* Thread Tags */,
1C29C3832258543200E1217A /* Threads */,
@@ -1455,7 +1448,6 @@
1AB84FD92ADC611B00E7334D /* bat.svg in Resources */,
8C0F4F571682CCFA00E25D7E /* macinyos-heading-right.png in Resources */,
1C8F67F822210DED007E61ED /* Profile.html.stencil in Resources */,
- 1CE2B76B19C2374C00FDC33E /* Login.storyboard in Resources */,
1CC10E3B1DD9558B00E0FB63 /* PotentiallyObjectionableThreadTags.plist in Resources */,
8C483FC216852E5D00D02482 /* macinyos-loading@2x.png in Resources */,
1C8F67FD2221BB57007E61ED /* PrivateMessage.html.stencil in Resources */,
@@ -1479,7 +1471,6 @@
1CA45D941F2C0AD1005BEEC5 /* RenderView.js in Resources */,
1C4EE1CE28470C2400A7507E /* Assets.xcassets in Resources */,
8C8813A218492AC80058009E /* mac-watch.png in Resources */,
- 1CF264CA1F7811EA0059CCCA /* RootTabBarController.storyboard in Resources */,
8C9834E2169091CE009F0CD6 /* fyad-bubble.png in Resources */,
1C41B8A716CD573D00718F79 /* title-banned.gif in Resources */,
1C1E57512C2521580075E3B6 /* platinum-member-white.png in Resources */,
@@ -1487,7 +1478,6 @@
2D19BA3929C33303009DD94F /* niggly60.json in Resources */,
1C41B8A816CD573D00718F79 /* title-permabanned.gif in Resources */,
8CE6100818CAAF74002E92DA /* cat.gif in Resources */,
- 1CDD0A4719B7D89B009811C4 /* LaunchScreen.storyboard in Resources */,
F4B3645819165D5100CCE1EF /* SecondaryTags.plist in Resources */,
1C82AC4D199F5C1500CB15FE /* Selectotron.xib in Resources */,
1C5C2C5922D2586D00EA5A80 /* ARChromeActivity.xcassets in Resources */,
@@ -1541,9 +1531,10 @@
83410EF219A582B8002CD019 /* DateFormatters.swift in Sources */,
1C273A9E21B316DB002875A9 /* LoadMoreFooter.swift in Sources */,
1C2C1F0E1CE16FE200CD27DD /* CloseBBcodeTagCommand.swift in Sources */,
- 30E0C51C2E35C89D0030DC0A /* SmilieData.swift in Sources */,
30E0C51D2E35C89D0030DC0A /* AnimatedImageView.swift in Sources */,
30E0C51E2E35C89D0030DC0A /* SmiliePickerView.swift in Sources */,
+ 2DBE262F2EDA765700E095A6 /* RootViewControllerRepresentable.swift in Sources */,
+ 2DBE26302EDA765700E095A6 /* AwfulApp.swift in Sources */,
30E0C51F2E35C89D0030DC0A /* SmilieSearchViewModel.swift in Sources */,
30E0C5202E35C89D0030DC0A /* SmilieGridItem.swift in Sources */,
1C16FBF31CBDC58B00C88BD1 /* URL+OpensInBrowser.swift in Sources */,
@@ -1593,7 +1584,6 @@
1CEB5BFF19AB9C1700C82C30 /* InAppActionViewController.swift in Sources */,
1CA3D6FC2D98A7E400D70964 /* OEmbedFetcher.swift in Sources */,
1C16FBAA1CB5D38700C88BD1 /* CompositionInputAccessoryView.swift in Sources */,
- 1C9AEBCE210C3BAF00C9A567 /* main.swift in Sources */,
1C16FBE71CBC671A00C88BD1 /* PostRenderModel.swift in Sources */,
1CB5F7F7201547D90046D080 /* Thread+Presentation.swift in Sources */,
1C16FC181CD1848400C88BD1 /* ComposeTextViewController.swift in Sources */,
From 084ea7ac39bb69271db52c136e1dc21de0f3b3ff Mon Sep 17 00:00:00 2001
From: commiekong <30882689+dfsm@users.noreply.github.com>
Date: Sat, 29 Nov 2025 12:05:47 +1100
Subject: [PATCH 2/2] Change print to logger
---
App/Main/AwfulApp.swift | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/App/Main/AwfulApp.swift b/App/Main/AwfulApp.swift
index 3689090a5..635c926fb 100644
--- a/App/Main/AwfulApp.swift
+++ b/App/Main/AwfulApp.swift
@@ -3,9 +3,12 @@
// Copyright 2025 Awful Contributors. CC BY-NC-SA 3.0 US https://github.com/Awful/Awful.app
import AwfulCore
+import os
import Smilies
import SwiftUI
+private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AwfulApp")
+
@main
struct AwfulApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@@ -41,7 +44,7 @@ struct AwfulApp: App {
do {
try appDelegate.managedObjectContext.save()
} catch {
- print("Failed to save on background: \(error)")
+ logger.error("Failed to save on background: \(error)")
}
@unknown default:
break