From 98dd6f3fe761916fd4027f1aab87c59054b928fe Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 4 Apr 2023 13:50:00 -0500 Subject: [PATCH 1/6] =?UTF-8?q?Fixes=20CollapsingTextView=20=E2=80=9CTextK?= =?UTF-8?q?it=201=20compatibility=20mode=E2=80=9D=20runtime=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AltStore/Components/CollapsingTextView.swift | 35 ++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/AltStore/Components/CollapsingTextView.swift b/AltStore/Components/CollapsingTextView.swift index be966c54..3392e3bb 100644 --- a/AltStore/Components/CollapsingTextView.swift +++ b/AltStore/Components/CollapsingTextView.swift @@ -22,7 +22,7 @@ final class CollapsingTextView: UITextView } } - var lineSpacing: CGFloat = 2 { + var lineSpacing: Double = 2 { didSet { self.setNeedsLayout() } @@ -34,7 +34,19 @@ final class CollapsingTextView: UITextView { super.awakeFromNib() - self.layoutManager.delegate = self + self.initialize() + } + + private func initialize() + { + if #available(iOS 16, *) + { + self.updateText() + } + else + { + self.layoutManager.delegate = self + } self.textContainerInset = .zero self.textContainer.lineFragmentPadding = 0 @@ -108,6 +120,25 @@ private extension CollapsingTextView { self.isCollapsed.toggle() } + + @available(iOS 16, *) + func updateText() + { + do + { + let style = NSMutableParagraphStyle() + style.lineSpacing = self.lineSpacing + + var attributedText = try AttributedString(self.attributedText, including: \.uiKit) + attributedText[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute.self] = style + + self.attributedText = NSAttributedString(attributedText) + } + catch + { + print("[ALTLog] Failed to update CollapsingTextView line spacing:", error) + } + } } extension CollapsingTextView: NSLayoutManagerDelegate From 822ea08d8901bd0d2f02ed09458cce0401536b14 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 2 Mar 2023 14:50:06 -0600 Subject: [PATCH 2/6] Fixes AppViewController deprecation warnings --- AltStore/App Detail/AppViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index 238b90b8..ffba2ba1 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -217,8 +217,8 @@ final class AppViewController: UIViewController self._shouldResetLayout = false } - - let statusBarHeight = UIApplication.shared.statusBarFrame.height + + let statusBarHeight = self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0 let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius let inset = 12 as CGFloat @@ -323,7 +323,7 @@ final class AppViewController: UIViewController self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY - self.scrollView.scrollIndicatorInsets.top = statusBarHeight + self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight // Adjust content offset + size. let contentOffset = self.scrollView.contentOffset From 36913b425cfbfe341687ac586324437c44274356 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 2 Mar 2023 15:23:23 -0600 Subject: [PATCH 3/6] =?UTF-8?q?Fixes=20=E2=80=9Cvariable=20mutated=20after?= =?UTF-8?q?=20capture=20by=20sendable=20closure=E2=80=9D=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AltStore/Operations/BackupAppOperation.swift | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/AltStore/Operations/BackupAppOperation.swift b/AltStore/Operations/BackupAppOperation.swift index 2cf17266..27fbb793 100644 --- a/AltStore/Operations/BackupAppOperation.swift +++ b/AltStore/Operations/BackupAppOperation.swift @@ -29,6 +29,9 @@ class BackupAppOperation: ResultOperation private var appName: String? private var timeoutTimer: Timer? + private weak var applicationWillReturnObserver: NSObjectProtocol? + private weak var backupResponseObserver: NSObjectProtocol? + init(action: Action, context: InstallAppOperationContext) { self.action = action @@ -153,8 +156,11 @@ private extension BackupAppOperation { func registerObservers() { - var applicationWillReturnObserver: NSObjectProtocol! - applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in + self.applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in + defer { + self?.applicationWillReturnObserver.map { NotificationCenter.default.removeObserver($0) } + } + guard let self = self, !self.isFinished else { return } self.timeoutTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] (timer) in @@ -166,18 +172,17 @@ private extension BackupAppOperation self.finish(.failure(OperationError.timedOut)) } } - - NotificationCenter.default.removeObserver(applicationWillReturnObserver!) } - var backupResponseObserver: NSObjectProtocol! - backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in + self.backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in + defer { + self?.backupResponseObserver.map { NotificationCenter.default.removeObserver($0) } + } + self?.timeoutTimer?.invalidate() let result = notification.userInfo?[AppDelegate.appBackupResultKey] as? Result ?? .failure(OperationError.unknownResult) self?.finish(result) - - NotificationCenter.default.removeObserver(backupResponseObserver!) } } } From f97548fc3a67e1de9daf7ae3eae91e863eb8633d Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 2 Mar 2023 15:27:31 -0600 Subject: [PATCH 4/6] Fixes UIDocumentPickerViewController deprecation warnings --- AltStore/My Apps/MyAppsViewController.swift | 32 +++++++-------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 9f968e16..b249ebf5 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -10,6 +10,7 @@ import UIKit import MobileCoreServices import Intents import Combine +import UniformTypeIdentifiers import AltStoreCore import AltSign @@ -701,18 +702,9 @@ private extension MyAppsViewController @IBAction func sideloadApp(_ sender: UIBarButtonItem) { - let supportedTypes: [String] + let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil) - if let types = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, "ipa" as CFString, nil)?.takeRetainedValue() - { - supportedTypes = (types as NSArray).map { $0 as! String } - } - else - { - supportedTypes = ["com.apple.itunes.ipa"] // Declared by the system. - } - - let documentPickerViewController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import) + let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true) documentPickerViewController.delegate = self self.present(documentPickerViewController, animated: true, completion: nil) } @@ -1246,8 +1238,11 @@ private extension MyAppsViewController { guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return } - let documentPicker = UIDocumentPickerViewController(url: backupURL, in: .exportToService) - documentPicker.delegate = self + let documentPicker = UIDocumentPickerViewController(forExporting: [backupURL], asCopy: true) + + // Don't set delegate to avoid conflicting with import callbacks. + // documentPicker.delegate = self + self.present(documentPicker, animated: true, completion: nil) } @@ -2050,15 +2045,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate { guard let fileURL = urls.first else { return } - switch controller.documentPickerMode - { - case .import, .open: - self.sideloadApp(at: fileURL) { (result) in - print("Sideloaded app at \(fileURL) with result:", result) - } - - case .exportToService, .moveToService: break - @unknown default: break + self.sideloadApp(at: fileURL) { (result) in + print("Sideloaded app at \(fileURL) with result:", result) } } } From cfcfc3e92848b19e3530317c17ecc47f793e6512 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 4 Apr 2023 13:29:48 -0500 Subject: [PATCH 5/6] Fixes incorrect cell height for some News items We need to take layoutMargins into account when calculating the height of the prototype cell. --- AltStore/News/NewsViewController.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AltStore/News/NewsViewController.swift b/AltStore/News/NewsViewController.swift index 347d605f..945a0822 100644 --- a/AltStore/News/NewsViewController.swift +++ b/AltStore/News/NewsViewController.swift @@ -426,6 +426,10 @@ extension NewsViewController: UICollectionViewDelegateFlowLayout return previousSize } + // Take layout margins into account. + self.prototypeCell.layoutMargins.left = self.view.layoutMargins.left + self.prototypeCell.layoutMargins.right = self.view.layoutMargins.right + let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width) NSLayoutConstraint.activate([widthConstraint]) defer { NSLayoutConstraint.deactivate([widthConstraint]) } From b986fae61106b1c614a37934368f9dfb511d20b4 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 4 Apr 2023 15:44:29 -0500 Subject: [PATCH 6/6] Enforces 77x31 minimum size for PillButton --- AltStore/Components/PillButton.swift | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/AltStore/Components/PillButton.swift b/AltStore/Components/PillButton.swift index 3527fe02..d56ad137 100644 --- a/AltStore/Components/PillButton.swift +++ b/AltStore/Components/PillButton.swift @@ -8,6 +8,12 @@ import UIKit +extension PillButton +{ + static let minimumSize = CGSize(width: 77, height: 31) + static let contentInsets = NSDirectionalEdgeInsets(top: 7, leading: 13, bottom: 7, trailing: 13) +} + final class PillButton: UIButton { override var accessibilityValue: String? { @@ -70,9 +76,7 @@ final class PillButton: UIButton }() override var intrinsicContentSize: CGSize { - var size = super.intrinsicContentSize - size.width += 26 - size.height += 3 + let size = self.sizeThatFits(CGSize(width: Double.infinity, height: .infinity)) return size } @@ -88,6 +92,8 @@ final class PillButton: UIButton self.layer.masksToBounds = true self.accessibilityTraits.formUnion([.updatesFrequently, .button]) + self.contentEdgeInsets = UIEdgeInsets(top: Self.contentInsets.top, left: Self.contentInsets.leading, bottom: Self.contentInsets.bottom, right: Self.contentInsets.trailing) + self.activityIndicatorView.style = .medium self.activityIndicatorView.isUserInteractionEnabled = false @@ -119,6 +125,15 @@ final class PillButton: UIButton self.update() } + + override func sizeThatFits(_ size: CGSize) -> CGSize + { + var size = super.sizeThatFits(size) + size.width = max(size.width, PillButton.minimumSize.width) + size.height = max(size.height, PillButton.minimumSize.height) + + return size + } } private extension PillButton