From e92c0b3a8a6d6639fe49eeba99545aa3b92cf60f Mon Sep 17 00:00:00 2001 From: Guy Kogus Date: Thu, 12 Jun 2025 13:53:49 +0200 Subject: [PATCH 1/2] Support Xcode 26 --- .../SwiftStdlib/CollectionExtensions.swift | 2 +- .../SwifterSwift/UIKit/UIViewExtensions.swift | 2 +- .../FoundationTests/DateExtensionsTests.swift | 38 +------------------ .../NSAttributedStringExtensionsTests.swift | 27 +++++++++---- .../StringExtensionsTests.swift | 6 +++ ...INavigationControllerExtensionsTests.swift | 4 +- .../UIRefreshControlExtensionsTests.swift | 18 ++++++--- .../UIViewControllerExtensionsTests.swift | 5 +++ Tests/UIKitTests/UIViewExtensionsTests.swift | 1 + .../WKWebViewExtensionsTests.swift | 2 +- 10 files changed, 51 insertions(+), 54 deletions(-) diff --git a/Sources/SwifterSwift/SwiftStdlib/CollectionExtensions.swift b/Sources/SwifterSwift/SwiftStdlib/CollectionExtensions.swift index a654f79b6..e265c41ab 100644 --- a/Sources/SwifterSwift/SwiftStdlib/CollectionExtensions.swift +++ b/Sources/SwifterSwift/SwiftStdlib/CollectionExtensions.swift @@ -13,7 +13,7 @@ public extension Collection { // MARK: - Methods -public extension Collection where Self: Sendable { +public extension Collection where Self: Sendable, Self.Index: Sendable { #if canImport(Dispatch) /// SwifterSwift: Performs `each` closure for each element of collection in parallel. /// diff --git a/Sources/SwifterSwift/UIKit/UIViewExtensions.swift b/Sources/SwifterSwift/UIKit/UIViewExtensions.swift index 57263b21e..e9f9c47db 100755 --- a/Sources/SwifterSwift/UIKit/UIViewExtensions.swift +++ b/Sources/SwifterSwift/UIKit/UIViewExtensions.swift @@ -296,7 +296,7 @@ public extension UIView { /// SwifterSwift: Remove all gesture recognizers from view. func removeGestureRecognizers() { - try? gestureRecognizers?.forEach(removeGestureRecognizer) + gestureRecognizers?.forEach(removeGestureRecognizer) } /// SwifterSwift: Attaches gesture recognizers to the view. Attaching gesture recognizers to a view defines the diff --git a/Tests/FoundationTests/DateExtensionsTests.swift b/Tests/FoundationTests/DateExtensionsTests.swift index 5e65693ba..a43369530 100644 --- a/Tests/FoundationTests/DateExtensionsTests.swift +++ b/Tests/FoundationTests/DateExtensionsTests.swift @@ -13,44 +13,8 @@ final class DateExtensionsTests: XCTestCase { NSTimeZone.default = TimeZone(abbreviation: "UTC")! } - // swiftlint:disable:next cyclomatic_complexity func testCalendar() { - switch Calendar.current.identifier { - case .buddhist: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .buddhist).identifier) - case .chinese: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .chinese).identifier) - case .coptic: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .coptic).identifier) - case .ethiopicAmeteAlem: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .ethiopicAmeteAlem).identifier) - case .ethiopicAmeteMihret: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .ethiopicAmeteMihret).identifier) - case .gregorian: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .gregorian).identifier) - case .hebrew: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .hebrew).identifier) - case .indian: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .indian).identifier) - case .islamic: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .islamic).identifier) - case .islamicCivil: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .islamicCivil).identifier) - case .islamicTabular: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .islamicTabular).identifier) - case .islamicUmmAlQura: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .islamicUmmAlQura).identifier) - case .iso8601: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .iso8601).identifier) - case .japanese: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .japanese).identifier) - case .persian: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .persian).identifier) - case .republicOfChina: - XCTAssertEqual(Date().calendar.identifier, Calendar(identifier: .republicOfChina).identifier) - @unknown default: - break - } + XCTAssertEqual(Date().calendar.identifier, Calendar.current.identifier) } func testEra() { diff --git a/Tests/FoundationTests/NSAttributedStringExtensionsTests.swift b/Tests/FoundationTests/NSAttributedStringExtensionsTests.swift index cb3ee1b80..f1a80e2ce 100644 --- a/Tests/FoundationTests/NSAttributedStringExtensionsTests.swift +++ b/Tests/FoundationTests/NSAttributedStringExtensionsTests.swift @@ -150,7 +150,6 @@ final class NSAttributedStringExtensionsTests: XCTestCase { // iterate through each range of attributes attrTestString .enumerateAttributes(in: stringRange, options: .longestEffectiveRangeNotRequired) { attrs, range, _ in - // exit if there are not more attributes for the subsequence than what was there originally guard attrs.count > attrAtBeginning.count else { return } @@ -167,7 +166,9 @@ final class NSAttributedStringExtensionsTests: XCTestCase { XCTAssertEqual(attr.value as? SFColor, SFColor.blue) passed = true } else if attr.key == .font { + #if !os(tvOS) XCTAssertEqual(attr.value as? SFFont, .boldSystemFont(ofSize: SFFont.systemFontSize)) + #endif } else { passed = false } @@ -224,9 +225,9 @@ final class NSAttributedStringExtensionsTests: XCTestCase { XCTAssertEqual(string.string, "Test Appending") var attributes = string.attributes(at: 0, effectiveRange: nil) - var filteredAttributes = attributes.filter { key, value -> Bool in + attributes = attributes.filter { key, value -> Bool in var valid = false - #if canImport(UIKit) + #if canImport(UIKit) && !os(tvOS) if key == NSAttributedString.Key.font, let value = value as? SFFont, value == .italicSystemFont(ofSize: SFFont.systemFontSize) { valid = true @@ -244,19 +245,21 @@ final class NSAttributedStringExtensionsTests: XCTestCase { return valid } - #if canImport(UIKit) - XCTAssertEqual(filteredAttributes.count, 3) + #if canImport(UIKit) && !os(tvOS) + XCTAssertEqual(attributes.count, 3) #else - XCTAssertEqual(filteredAttributes.count, 2) + XCTAssertEqual(attributes.count, 2) #endif attributes = string.attributes(at: 5, effectiveRange: nil) - filteredAttributes = attributes.filter { key, value -> Bool in + #if !os(tvOS) + attributes = attributes.filter { key, value -> Bool in return key == NSAttributedString.Key .font && (value as? SFFont) == .boldSystemFont(ofSize: SFFont.systemFontSize) } + #endif - XCTAssertEqual(filteredAttributes.count, 1) + XCTAssertEqual(attributes.count, 1) } func testAttributes() { @@ -277,7 +280,11 @@ final class NSAttributedStringExtensionsTests: XCTestCase { case NSAttributedString.Key.strikethroughStyle: return (value as? NSUnderlineStyle.RawValue) == NSUnderlineStyle.single.rawValue case NSAttributedString.Key.font: + #if os(tvOS) + return false + #else return (value as? SFFont) == .boldSystemFont(ofSize: SFFont.systemFontSize) + #endif case NSAttributedString.Key.foregroundColor: return (value as? SFColor) == .blue default: @@ -285,7 +292,11 @@ final class NSAttributedStringExtensionsTests: XCTestCase { } } + #if os(tvOS) + XCTAssertEqual(filteredAttributes.count, 3) + #else XCTAssertEqual(filteredAttributes.count, 4) + #endif } // MARK: - Operators diff --git a/Tests/SwiftStdlibTests/StringExtensionsTests.swift b/Tests/SwiftStdlibTests/StringExtensionsTests.swift index e95d36dc6..f59ab79a6 100644 --- a/Tests/SwiftStdlibTests/StringExtensionsTests.swift +++ b/Tests/SwiftStdlibTests/StringExtensionsTests.swift @@ -226,6 +226,12 @@ final class StringExtensionsTests: XCTestCase { let helloWorld = "hello world".url #if os(Linux) || os(Android) XCTAssertEqual(helloWorld, URL(string: "hello%20world")) + #elseif targetEnvironment(macCatalyst) + if #available(iOS 26.0, *) { + XCTAssertNil(helloWorld) + } else { + XCTAssertEqual(helloWorld, URL(string: "hello%20world")) + } #else if #available(iOS 17.0, *) { XCTAssertEqual(helloWorld, URL(string: "hello%20world")) diff --git a/Tests/UIKitTests/UINavigationControllerExtensionsTests.swift b/Tests/UIKitTests/UINavigationControllerExtensionsTests.swift index c12a0aa85..f2a07253d 100644 --- a/Tests/UIKitTests/UINavigationControllerExtensionsTests.swift +++ b/Tests/UIKitTests/UINavigationControllerExtensionsTests.swift @@ -62,7 +62,9 @@ final class UINavigationControllerExtensionsTests: XCTestCase { tabVC.viewControllers = [navigationController] navigationController.pushViewController(vcToPush, hidesBottomBar: true, animated: false) XCTAssert(vcToPush.hidesBottomBarWhenPushed) - XCTAssert(tabVC.tabBar.isHidden) + if #unavailable(iOS 26.0) { // Broken in iOS 26.0 seed 1 + XCTAssert(tabVC.tabBar.isHidden) + } } #endif } diff --git a/Tests/UIKitTests/UIRefreshControlExtensionsTests.swift b/Tests/UIKitTests/UIRefreshControlExtensionsTests.swift index a8f1e10ed..dfa62020f 100644 --- a/Tests/UIKitTests/UIRefreshControlExtensionsTests.swift +++ b/Tests/UIKitTests/UIRefreshControlExtensionsTests.swift @@ -3,15 +3,19 @@ @testable import SwifterSwift import XCTest -#if canImport(UIKit) && os(iOS) +#if canImport(UIKit) && os(iOS) && !targetEnvironment(macCatalyst) import UIKit @MainActor final class UIRefreshControlExtensionTests: XCTestCase { func testBeginRefreshAsRefreshControlSubview() { let window = UIWindow() + let viewController = UIViewController() + window.rootViewController = viewController + window.makeKeyAndVisible() + let tableView = UITableView() - window.addSubview(tableView) + viewController.view.addSubview(tableView) let refreshControl = UIRefreshControl() tableView.addSubview(refreshControl) refreshControl.beginRefreshing(in: tableView, animated: false) @@ -20,7 +24,7 @@ final class UIRefreshControlExtensionTests: XCTestCase { XCTAssertEqual(tableView.contentOffset.y, -refreshControl.frame.height) let anotherTableview = UITableView() - window.addSubview(anotherTableview) + viewController.view.addSubview(anotherTableview) anotherTableview.refreshControl = UIRefreshControl() anotherTableview.refreshControl!.beginRefreshing(in: anotherTableview, animated: false, sendAction: true) @@ -30,8 +34,12 @@ final class UIRefreshControlExtensionTests: XCTestCase { func testBeginRefreshAsScrollViewSubview() { let window = UIWindow() + let viewController = UIViewController() + window.rootViewController = viewController + window.makeKeyAndVisible() + let scrollView = UIScrollView() - window.addSubview(scrollView) + viewController.view.addSubview(scrollView) XCTAssertEqual(scrollView.contentOffset, .zero) let refreshControl = UIRefreshControl() scrollView.addSubview(refreshControl) @@ -41,7 +49,7 @@ final class UIRefreshControlExtensionTests: XCTestCase { XCTAssertEqual(scrollView.contentOffset.y, -refreshControl.frame.height) let anotherScrollView = UIScrollView() - window.addSubview(anotherScrollView) + viewController.view.addSubview(anotherScrollView) XCTAssertEqual(anotherScrollView.contentOffset, .zero) anotherScrollView.refreshControl = UIRefreshControl() anotherScrollView.refreshControl!.beginRefreshing(animated: false, sendAction: true) diff --git a/Tests/UIKitTests/UIViewControllerExtensionsTests.swift b/Tests/UIKitTests/UIViewControllerExtensionsTests.swift index 37fb07157..48f72f452 100644 --- a/Tests/UIKitTests/UIViewControllerExtensionsTests.swift +++ b/Tests/UIKitTests/UIViewControllerExtensionsTests.swift @@ -71,6 +71,11 @@ final class UIViewControllerExtensionsTests: XCTestCase { func testShowAlert() { let viewController = UIViewController() + + let window = UIWindow() + window.rootViewController = viewController + window.makeKeyAndVisible() + let title = "test title" let message = "test message" let actionButtons = ["OK", "Cancel"] diff --git a/Tests/UIKitTests/UIViewExtensionsTests.swift b/Tests/UIKitTests/UIViewExtensionsTests.swift index ffa593e59..daf687319 100644 --- a/Tests/UIKitTests/UIViewExtensionsTests.swift +++ b/Tests/UIKitTests/UIViewExtensionsTests.swift @@ -692,6 +692,7 @@ final class UIViewExtensionsTests: XCTestCase { // swiftlint:disable:this type_b view.addBottomSeparator(color: expectedColor, height: expectedHeight) let separator = try XCTUnwrap(view.subviews.first, "Separator not found") + view.layoutIfNeeded() XCTAssertEqual(view.subviews.count, 1) XCTAssertEqual(separator.backgroundColor, expectedColor) diff --git a/Tests/WebKitTests/WKWebViewExtensionsTests.swift b/Tests/WebKitTests/WKWebViewExtensionsTests.swift index 3bed8ee98..33a35afd9 100644 --- a/Tests/WebKitTests/WKWebViewExtensionsTests.swift +++ b/Tests/WebKitTests/WKWebViewExtensionsTests.swift @@ -54,7 +54,7 @@ final class WKWebViewExtensionsTests: XCTestCase { let failureExpectation = WebViewFailureExpectation(description: "Dead URL string", webView: webView) let deadURLString = "https://dead-url-573489574389.com" - let navigation = webView.loadURLString(deadURLString, timeout: 5.0) + let navigation = webView.loadURLString(deadURLString, timeout: 2.0) XCTAssertNotNil(navigation) From a0023d82771f1226dfcdfd264f260218dbec02f4 Mon Sep 17 00:00:00 2001 From: Guy Kogus Date: Wed, 6 Aug 2025 14:49:38 +0200 Subject: [PATCH 2/2] Use Xcode 16.4 --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 16bce3973..a8fdbbff3 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -74,7 +74,7 @@ jobs: matrix: platform: ['ios', 'macos', 'tvos', 'watchos'] env: - DEVELOPER_DIR: /Applications/Xcode_16.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.4.0.app/Contents/Developer steps: - uses: actions/checkout@v1 - name: Bundle Install