From 14a4ef4590dcd49a67d37ac6c83474a2696f1928 Mon Sep 17 00:00:00 2001 From: maximkrouk Date: Thu, 29 Feb 2024 02:18:40 +0100 Subject: [PATCH] feat: Prepare release 0.4.0 - Use macros for CustomView/CustomWindow - Fix build issues on watchOS - Setup CI - Add more tests - Use tabs for indentation - Update dependencies - Fix ReuseIDProvidingType warnings --- .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/CocoaExtensions.xcscheme | 66 +++ .../xcschemes/CocoaExtensionsMacros.xcscheme | 66 +++ .../CocoaExtensionsMacrosPluginTests.xcscheme | 53 +++ .../CocoaExtensionsMacrosTests.xcscheme | 53 +++ .../xcschemes/CocoaExtensionsTests.xcscheme | 53 +++ .../swift-cocoa-extensions-Package.xcscheme | 54 ++- .github/workflows/ci.yml | 34 ++ .spi.yml | 5 + .../xcschemes/CocoaExtensions.xcscheme | 66 +++ .../xcschemes/CocoaExtensionsMacros.xcscheme | 66 +++ .../CocoaExtensionsMacrosPluginTests.xcscheme | 53 +++ .../CocoaExtensionsMacrosTests.xcscheme | 53 +++ .../xcschemes/CocoaExtensionsTests.xcscheme | 53 +++ .../swift-cocoa-extensions-Package.xcscheme | 154 +++++++ .../xcschemes/CocoaExtensions.xcscheme | 2 +- .../xcschemes/CocoaExtensionsMacros.xcscheme | 66 +++ .../CocoaExtensionsMacrosPluginTests.xcscheme | 53 +++ .../CocoaExtensionsMacrosTests.xcscheme | 2 +- .../xcschemes/CocoaExtensionsTests.xcscheme | 53 +++ .../swift-cocoa-extensions-Package.xcscheme | 154 +++++++ Makefile | 71 ++++ Package.swift | 29 +- README.md | 12 +- .../Components/Toolbar/ToolbarItem.swift | 8 +- .../CusomCocoaViewController.swift | 37 +- .../CustomCocoaWindowController.swift | 6 +- .../Controllers/CustomHostingController.swift | 36 +- .../CustomNavigationController.swift | 16 +- .../Controllers/CustomTabBarController.swift | 16 +- .../CustomCocoaViewControllerProtocol.swift | 17 +- .../CustomCocoaWindowControllerProtocol.swift | 4 +- .../Protocols/CustomCocoaWindowProtocol.swift | 2 +- .../Custom/Views/CustomCocoaWindow.swift | 2 +- .../Views/CustomCollectionViewCell.swift | 25 +- .../Extensions/CocoaApplication+.swift | 24 +- .../Extensions/CocoaReuse.swift | 298 +++++++------- .../Extensions/CompositionalLayout.swift | 22 +- .../Extensions/CoreGraphics/CGPoint+.swift | 6 +- .../Extensions/CoreGraphics/CGSize+.swift | 22 +- .../External/Responder++.swift | 162 ++++---- Sources/CocoaExtensions/Macros.swift | 246 ----------- .../CustomViewMacroFallback.swift | 130 ++++++ .../CustomWindowMacroFallback.swift | 97 +++++ .../SwiftUI/CocoaComponent.swift | 381 +++++++++++------- .../CocoaExtensions/SwiftUI/HostingView.swift | 110 ++--- Sources/CocoaExtensionsMacros/Exports.swift | 2 + .../Helpers/Diagnostics+.swift | 11 - .../Helpers/Operators.swift | 43 -- .../Helpers/Result+.swift | 1 - Sources/CocoaExtensionsMacros/Macros.swift | 17 + .../CustomViewMacro/CustomViewMacro.swift | 2 +- .../CustomWindowMacro/CustomWindowMacro.swift | 2 +- .../Helpers/Diagnostics+.swift | 11 + .../Helpers/Operators.swift | 43 ++ .../Internal/CustomPropertyMacro.swift | 0 .../Internal/Extensions/Type+.swift | 0 .../Plugin.swift | 0 .../CustomViewTests.swift | 191 +++++++++ .../CustomWindowTests.swift | 163 ++++++++ .../CustomViewTests.swift | 165 +------- .../CustomWindowTests.swift | 167 +------- .../CocoaExtensionsTests.swift | 22 - .../CoreGraphicsTests.swift | 24 ++ .../CustomViewMacroFallbackTests.swift | 16 + .../CustomWindowMacroFallbackTests.swift | 18 + 67 files changed, 2652 insertions(+), 1199 deletions(-) create mode 100644 .github/package.xcworkspace/contents.xcworkspacedata create mode 100644 .github/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 .github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensions.xcscheme create mode 100644 .github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme create mode 100644 .github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme create mode 100644 .github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme create mode 100644 .github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme rename .swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions.xcscheme => .github/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme (62%) create mode 100644 .github/workflows/ci.yml create mode 100644 .spi.yml create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensions.xcscheme create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme create mode 100644 Makefile delete mode 100644 Sources/CocoaExtensions/Macros.swift create mode 100644 Sources/CocoaExtensions/PropertyWrappers/CustomViewMacroFallback.swift create mode 100644 Sources/CocoaExtensions/PropertyWrappers/CustomWindowMacroFallback.swift create mode 100644 Sources/CocoaExtensionsMacros/Exports.swift delete mode 100644 Sources/CocoaExtensionsMacros/Helpers/Diagnostics+.swift delete mode 100644 Sources/CocoaExtensionsMacros/Helpers/Operators.swift delete mode 100644 Sources/CocoaExtensionsMacros/Helpers/Result+.swift create mode 100644 Sources/CocoaExtensionsMacros/Macros.swift rename Sources/{CocoaExtensionsMacros => CocoaExtensionsMacrosPlugin}/CustomViewMacro/CustomViewMacro.swift (96%) rename Sources/{CocoaExtensionsMacros => CocoaExtensionsMacrosPlugin}/CustomWindowMacro/CustomWindowMacro.swift (96%) create mode 100644 Sources/CocoaExtensionsMacrosPlugin/Helpers/Diagnostics+.swift create mode 100644 Sources/CocoaExtensionsMacrosPlugin/Helpers/Operators.swift rename Sources/{CocoaExtensionsMacros => CocoaExtensionsMacrosPlugin}/Internal/CustomPropertyMacro.swift (100%) rename Sources/{CocoaExtensionsMacros => CocoaExtensionsMacrosPlugin}/Internal/Extensions/Type+.swift (100%) rename Sources/{CocoaExtensionsMacros => CocoaExtensionsMacrosPlugin}/Plugin.swift (100%) create mode 100644 Tests/CocoaExtensionsMacrosPluginTests/CustomViewTests.swift create mode 100644 Tests/CocoaExtensionsMacrosPluginTests/CustomWindowTests.swift delete mode 100644 Tests/CocoaExtensionsTests/CocoaExtensionsTests.swift create mode 100644 Tests/CocoaExtensionsTests/CoreGraphicsTests.swift create mode 100644 Tests/CocoaExtensionsTests/CustomViewMacroFallbackTests.swift create mode 100644 Tests/CocoaExtensionsTests/CustomWindowMacroFallbackTests.swift diff --git a/.github/package.xcworkspace/contents.xcworkspacedata b/.github/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..0fd0bc3 --- /dev/null +++ b/.github/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.github/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.github/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/.github/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensions.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensions.xcscheme new file mode 100644 index 0000000..421d12b --- /dev/null +++ b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensions.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme new file mode 100644 index 0000000..ff1dc50 --- /dev/null +++ b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme new file mode 100644 index 0000000..c1cf0ad --- /dev/null +++ b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme new file mode 100644 index 0000000..d9ee89a --- /dev/null +++ b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme new file mode 100644 index 0000000..3fa5ac4 --- /dev/null +++ b/.github/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme similarity index 62% rename from .swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions.xcscheme rename to .github/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme index fe8be4c..e9cb692 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions.xcscheme +++ b/.github/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme new file mode 100644 index 0000000..ff1dc50 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacros.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme new file mode 100644 index 0000000..c1cf0ad --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme new file mode 100644 index 0000000..d9ee89a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme new file mode 100644 index 0000000..3fa5ac4 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/CocoaExtensionsTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme new file mode 100644 index 0000000..e9cb692 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensions.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensions.xcscheme index 3c65ce1..421d12b 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensions.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensions.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme new file mode 100644 index 0000000..c1cf0ad --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosPluginTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme index c8fca48..d9ee89a 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/CocoaExtensionsMacrosTests.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme new file mode 100644 index 0000000..e9cb692 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-cocoa-extensions-Package.xcscheme @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..75f5e54 --- /dev/null +++ b/Makefile @@ -0,0 +1,71 @@ +CONFIG = debug +PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS 17.2,iPhone \d\+ Pro [^M]) +PLATFORM_MACOS = macOS +PLATFORM_MAC_CATALYST = macOS,variant=Mac Catalyst +PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,tvOS 17,TV) +PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,watchOS 10,Watch) + +default: test-all + +test-all: + $(MAKE) test + $(MAKE) test-docs + +test: + $(MAKE) CONFIG=debug test-library + $(MAKE) CONFIG=debug test-library-macros + $(MAKE) test-macros + +test-library: + for platform in "$(PLATFORM_IOS)" "$(PLATFORM_MACOS)" "$(PLATFORM_MAC_CATALYST)" "$(PLATFORM_TVOS)" "$(PLATFORM_WATCHOS)"; do \ + echo "\nTesting library on $$platform\n" && \ + (xcodebuild test \ + -skipMacroValidation \ + -configuration $(CONFIG) \ + -workspace .github/package.xcworkspace \ + -scheme CocoaExtensionsTests \ + -destination platform="$$platform" | xcpretty && exit 0 \ + ) \ + || exit 1; \ + done; + +test-library-macros: + for platform in "$(PLATFORM_IOS)" "$(PLATFORM_MACOS)" "$(PLATFORM_MAC_CATALYST)" "$(PLATFORM_TVOS)" "$(PLATFORM_WATCHOS)"; do \ + echo "\nTesting library-macros on $$platform\n" && \ + (xcodebuild test \ + -skipMacroValidation \ + -configuration $(CONFIG) \ + -workspace .github/package.xcworkspace \ + -scheme CocoaExtensionsMacrosTests \ + -destination platform="$$platform" | xcpretty && exit 0 \ + ) \ + || exit 1; \ + done; + +test-macros: + echo "\nTesting macros\n" && \ + (xcodebuild test \ + -skipMacroValidation \ + -configuration $(CONFIG) \ + -workspace .github/package.xcworkspace \ + -scheme CocoaExtensionsMacrosPluginTests \ + -destination platform=macOS | xcpretty && exit 0 \ + ) \ + || exit 1; + +DOC_WARNINGS = $(shell xcodebuild clean docbuild \ + -scheme CocoaExtensions \ + -destination platform="$(PLATFORM_IOS)" \ + -quiet \ + 2>&1 \ + | grep "couldn't be resolved to known documentation" \ + | sed 's|$(PWD)|.|g' \ + | tr '\n' '\1') +test-docs: + @test "$(DOC_WARNINGS)" = "" \ + || (echo "xcodebuild docbuild failed:\n\n$(DOC_WARNINGS)" | tr '\1' '\n' \ + && exit 1) + +define udid_for +$(shell xcrun simctl list devices available '$(1)' | grep '$(2)' | sort -r | head -1 | awk -F '[()]' '{ print $$(NF-3) }') +endef diff --git a/Package.swift b/Package.swift index 43aa7fd..2cea86a 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,7 @@ let package = Package( name: "swift-cocoa-extensions", platforms: [ .macOS(.v11), + .macCatalyst(.v13), .iOS(.v13), .tvOS(.v13), .watchOS(.v6) @@ -16,6 +17,10 @@ let package = Package( name: "CocoaExtensions", targets: ["CocoaExtensions"] ), + .library( + name: "CocoaExtensionsMacros", + targets: ["CocoaExtensionsMacros"] + ), ], dependencies: [ .package( @@ -28,7 +33,7 @@ let package = Package( ), .package( url: "https://github.com/capturecontext/swift-foundation-extensions.git", - .upToNextMinor(from: "0.4.0") + branch: "main" ), .package( url: "https://github.com/pointfreeco/swift-identified-collections.git", @@ -47,7 +52,6 @@ let package = Package( .target( name: "CocoaExtensions", dependencies: [ - .target(name: "CocoaExtensionsMacros"), .product( name: "CocoaAliases", package: "cocoa-aliases" @@ -66,8 +70,19 @@ let package = Package( ) ] ), - .macro( + .target( name: "CocoaExtensionsMacros", + dependencies: [ + .target(name: "CocoaExtensions"), + .target(name: "CocoaExtensionsMacrosPlugin"), + .product( + name: "FoundationExtensionsMacros", + package: "swift-foundation-extensions" + ), + ] + ), + .macro( + name: "CocoaExtensionsMacrosPlugin", dependencies: [ .product( name: "MacroToolkit", @@ -84,7 +99,13 @@ let package = Package( .testTarget( name: "CocoaExtensionsMacrosTests", dependencies: [ - .target(name: "CocoaExtensionsMacros"), + .target(name: "CocoaExtensionsMacros") + ] + ), + .testTarget( + name: "CocoaExtensionsMacrosPluginTests", + dependencies: [ + .target(name: "CocoaExtensionsMacrosPlugin"), .product(name: "MacroTesting", package: "swift-macro-testing"), ] ), diff --git a/README.md b/README.md index 4febde1..3916265 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # swift-cocoa-extensions -[![SwiftPM 5.8](https://img.shields.io/badge/swiftpm-5.8-ED523F.svg?style=flat)](https://swift.org/download/) ![Platforms](https://img.shields.io/badge/Platforms-iOS_13_|_macOS_10.15_|_Catalyst_13_|_tvOS_14_|_watchOS_7-ED523F.svg?style=flat) [![@maximkrouk](https://img.shields.io/badge/contact-@capturecontext-1DA1F2.svg?style=flat&logo=twitter)](https://twitter.com/capture_context) +[![SwiftPM 5.9](https://img.shields.io/badge/swiftpm-5.9-ED523F.svg?style=flat)](https://swift.org/download/) ![Platforms](https://img.shields.io/badge/Platforms-iOS_13_|_macOS_10.15_|_Catalyst_13_|_tvOS_14_|_watchOS_7-ED523F.svg?style=flat) [![@maximkrouk](https://img.shields.io/badge/contact-@capturecontext-1DA1F2.svg?style=flat&logo=twitter)](https://twitter.com/capture_context) Standard extensions for Cocoa @@ -27,7 +27,7 @@ If you use SwiftPM for your project, you can add StandardExtensions to your pack .package( name: "swift-cocoa-extensions", url: "https://github.com/capturecontext/swift-cocoa-extensions.git", - .upToNextMinor(from: "0.3.0") + .upToNextMinor(from: "0.4.0") ) ``` @@ -40,6 +40,14 @@ Do not forget about target dependencies: ) ``` +```swift +.product( + name: "CocoaExtensionsMacros", + package: "swift-cocoa-extensions" +) +``` + + ## License diff --git a/Sources/CocoaExtensions/Components/Toolbar/ToolbarItem.swift b/Sources/CocoaExtensions/Components/Toolbar/ToolbarItem.swift index b0cd77e..5f914c4 100644 --- a/Sources/CocoaExtensions/Components/Toolbar/ToolbarItem.swift +++ b/Sources/CocoaExtensions/Components/Toolbar/ToolbarItem.swift @@ -4,7 +4,7 @@ import FoundationExtensions import DeclarativeConfiguration public protocol ToolbarItemProtocol: NSToolbarItem { - var onAction: Handler.Container { get } + var onAction: Handler1.Container { get } var isSelectable: Bool { get } } @@ -33,7 +33,7 @@ public class SidebarTrackingToolbarItem: NSTrackingSeparatorToolbarItem, Toolbar self.action = #selector(__action) } - @Handler + @Handler1 public var onAction @objc @@ -59,7 +59,7 @@ public class ToolbarItem: NSToolbarItem, ToolbarItemProtocol, Identifiable { self.action = #selector(__action) } - @Handler + @Handler1 public var onAction @objc @@ -95,7 +95,7 @@ public class ToolbarItemGroup: NSToolbarItemGroup, ToolbarItemGroupProtocol, Ide self.action = #selector(__action) } - @Handler + @Handler1 public var onAction @objc diff --git a/Sources/CocoaExtensions/Custom/Controllers/CusomCocoaViewController.swift b/Sources/CocoaExtensions/Custom/Controllers/CusomCocoaViewController.swift index d97173d..37db4db 100644 --- a/Sources/CocoaExtensions/Custom/Controllers/CusomCocoaViewController.swift +++ b/Sources/CocoaExtensions/Custom/Controllers/CusomCocoaViewController.swift @@ -17,37 +17,34 @@ open class CustomCocoaViewController: _overrideNavigationController() ?? super.navigationController } - @Handler + @Handler1 public var onDismiss - @Handler + @Handler1 public var onViewDidLoad - @Handler + @Handler1 public var onViewWillAppear - @Handler + @Handler1 public var onViewDidAppear - @Handler + @Handler1 public var onViewWillDisappear - @Handler + @Handler1 public var onViewDidDisappear - @Handler + @Handler1 public var onViewWillLayout - @Handler + @Handler1 public var onViewDidLayout - #if !canImport(CocoaExtensionsMacros) open override func loadView() { guard !tryLoadCustomContentView() else { return } super.loadView() } - #endif - open override func viewDidLoad() { super.viewDidLoad() @@ -113,36 +110,34 @@ open class CustomCocoaViewController: { private(set) open var isVisible = false - @Handler + @Handler1 public var onDismiss - @Handler + @Handler1 public var onViewDidLoad - @Handler + @Handler1 public var onViewWillAppear - @Handler + @Handler1 public var onViewDidAppear - @Handler + @Handler1 public var onViewWillDisappear - @Handler + @Handler1 public var onViewDidDisappear - @Handler + @Handler1 public var onViewWillLayout - @Handler + @Handler1 public var onViewDidLayout - #if !canImport(CocoaExtensionsMacros) open override func loadView() { guard !tryLoadCustomContentView() else { return } super.loadView() } - #endif open override func viewDidLoad() { super.viewDidLoad() diff --git a/Sources/CocoaExtensions/Custom/Controllers/CustomCocoaWindowController.swift b/Sources/CocoaExtensions/Custom/Controllers/CustomCocoaWindowController.swift index 17b180d..659f8b2 100644 --- a/Sources/CocoaExtensions/Custom/Controllers/CustomCocoaWindowController.swift +++ b/Sources/CocoaExtensions/Custom/Controllers/CustomCocoaWindowController.swift @@ -6,10 +6,10 @@ open class CustomCocoaWindowController: NSWindowController, CustomCocoaWindowControllerProtocol { - @Handler + @Handler1 public var onWindowWillLoad - @Handler + @Handler1 public var onWindowDidLoad /// Use `override _init` instead of overriding this initializer @@ -24,12 +24,10 @@ open class CustomCocoaWindowController: self._init() } - #if !canImport(CocoaExtensionsMacros) open override func loadWindow() { guard !tryLoadCustomWindow() else { return } super.loadWindow() } - #endif open func _init() {} diff --git a/Sources/CocoaExtensions/Custom/Controllers/CustomHostingController.swift b/Sources/CocoaExtensions/Custom/Controllers/CustomHostingController.swift index 4a8f5bd..0fd332f 100644 --- a/Sources/CocoaExtensions/Custom/Controllers/CustomHostingController.swift +++ b/Sources/CocoaExtensions/Custom/Controllers/CustomHostingController.swift @@ -1,4 +1,4 @@ -#if canImport(SwiftUI) +#if canImport(SwiftUI) && !os(watchOS) import DeclarativeConfiguration import CocoaAliases import SwiftUI @@ -9,7 +9,7 @@ extension CocoaHostingController where Content: ExpressibleByNilLiteral { } } -#if canImport(UIKit) && !os(watchOS) +#if canImport(UIKit) extension CustomHostingController: NavigationControllerDynamicOverridable {} @@ -26,28 +26,28 @@ open class CustomHostingController: _overrideNavigationController() ?? super.navigationController } - @Handler + @Handler1 public var onDismiss - @Handler + @Handler1 public var onViewDidLoad - @Handler + @Handler1 public var onViewWillAppear - @Handler + @Handler1 public var onViewDidAppear - @Handler + @Handler1 public var onViewWillDisappear - @Handler + @Handler1 public var onViewDidDisappear - @Handler + @Handler1 public var onViewWillLayout - @Handler + @Handler1 public var onViewDidLayout open override func viewDidLoad() { @@ -125,28 +125,28 @@ open class CustomHostingController: { private(set) open var isVisible = false - @Handler + @Handler1 public var onDismiss - @Handler + @Handler1 public var onViewDidLoad - @Handler + @Handler1 public var onViewWillAppear - @Handler + @Handler1 public var onViewDidAppear - @Handler + @Handler1 public var onViewWillDisappear - @Handler + @Handler1 public var onViewDidDisappear - @Handler + @Handler1 public var onViewWillLayout - @Handler + @Handler1 public var onViewDidLayout open override func viewDidLoad() { diff --git a/Sources/CocoaExtensions/Custom/Controllers/CustomNavigationController.swift b/Sources/CocoaExtensions/Custom/Controllers/CustomNavigationController.swift index 0898099..1fc661d 100644 --- a/Sources/CocoaExtensions/Custom/Controllers/CustomNavigationController.swift +++ b/Sources/CocoaExtensions/Custom/Controllers/CustomNavigationController.swift @@ -17,28 +17,28 @@ open class CustomNavigationController: _overrideNavigationController() ?? super.navigationController } - @Handler + @Handler1 public var onDismiss - @Handler + @Handler1 public var onViewDidLoad - @Handler + @Handler1 public var onViewWillAppear - @Handler + @Handler1 public var onViewDidAppear - @Handler + @Handler1 public var onViewWillDisappear - @Handler + @Handler1 public var onViewDidDisappear - @Handler + @Handler1 public var onViewWillLayout - @Handler + @Handler1 public var onViewDidLayout open override func viewDidLoad() { diff --git a/Sources/CocoaExtensions/Custom/Controllers/CustomTabBarController.swift b/Sources/CocoaExtensions/Custom/Controllers/CustomTabBarController.swift index 167f8c8..ec4a333 100644 --- a/Sources/CocoaExtensions/Custom/Controllers/CustomTabBarController.swift +++ b/Sources/CocoaExtensions/Custom/Controllers/CustomTabBarController.swift @@ -17,28 +17,28 @@ open class CustomTabBarController: _overrideNavigationController() ?? super.navigationController } - @Handler + @Handler1 public var onDismiss - @Handler + @Handler1 public var onViewDidLoad - @Handler + @Handler1 public var onViewWillAppear - @Handler + @Handler1 public var onViewDidAppear - @Handler + @Handler1 public var onViewWillDisappear - @Handler + @Handler1 public var onViewDidDisappear - @Handler + @Handler1 public var onViewWillLayout - @Handler + @Handler1 public var onViewDidLayout open override func viewDidLoad() { diff --git a/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaViewControllerProtocol.swift b/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaViewControllerProtocol.swift index a993c0e..6b2950c 100644 --- a/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaViewControllerProtocol.swift +++ b/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaViewControllerProtocol.swift @@ -6,13 +6,14 @@ public protocol CustomCocoaViewControllerProtocol: CocoaViewController, CustomNSObjectProtocol { - var onDismiss: Handler.Container { get set } - var onViewDidLoad: Handler.Container { get set } - var onViewWillAppear: Handler.Container { get set } - var onViewDidAppear: Handler.Container { get set } - var onViewWillDisappear: Handler.Container { get set } - var onViewDidDisappear: Handler.Container { get set } - var onViewWillLayout: Handler.Container { get set } - var onViewDidLayout: Handler.Container { get set } + var isVisible: Bool { get } + var onDismiss: Handler1.Container { get set } + var onViewDidLoad: Handler1.Container { get set } + var onViewWillAppear: Handler1.Container { get set } + var onViewDidAppear: Handler1.Container { get set } + var onViewWillDisappear: Handler1.Container { get set } + var onViewDidDisappear: Handler1.Container { get set } + var onViewWillLayout: Handler1.Container { get set } + var onViewDidLayout: Handler1.Container { get set } } #endif diff --git a/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowControllerProtocol.swift b/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowControllerProtocol.swift index 1583300..4817e29 100644 --- a/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowControllerProtocol.swift +++ b/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowControllerProtocol.swift @@ -6,7 +6,7 @@ public protocol CustomCocoaWindowControllerProtocol: NSWindowController, CustomNSObjectProtocol { - var onWindowWillLoad: Handler.Container { get set } - var onWindowDidLoad: Handler.Container { get set } + var onWindowWillLoad: Handler1.Container { get set } + var onWindowDidLoad: Handler1.Container { get set } } #endif diff --git a/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowProtocol.swift b/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowProtocol.swift index 8ac6089..02b4009 100644 --- a/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowProtocol.swift +++ b/Sources/CocoaExtensions/Custom/Protocols/CustomCocoaWindowProtocol.swift @@ -4,7 +4,7 @@ import FunctionalClosures #if os(macOS) public protocol CustomCocoaWindowProtocol: CustomNSObjectProtocol { - var onClose: Handler.Container { get set } + var onClose: Handler1.Container { get set } } #elseif canImport(UIKit) public protocol CustomCocoaWindowProtocol: CustomCocoaViewProtocol {} diff --git a/Sources/CocoaExtensions/Custom/Views/CustomCocoaWindow.swift b/Sources/CocoaExtensions/Custom/Views/CustomCocoaWindow.swift index dafc2a4..481f0c3 100644 --- a/Sources/CocoaExtensions/Custom/Views/CustomCocoaWindow.swift +++ b/Sources/CocoaExtensions/Custom/Views/CustomCocoaWindow.swift @@ -4,7 +4,7 @@ import FunctionalClosures #if os(macOS) open class CustomCocoaWindow: CocoaWindow, CustomCocoaWindowProtocol { - @Handler + @Handler1 public var onClose public override func close() { diff --git a/Sources/CocoaExtensions/Custom/Views/CustomCollectionViewCell.swift b/Sources/CocoaExtensions/Custom/Views/CustomCollectionViewCell.swift index 9aeaa91..6fcf394 100644 --- a/Sources/CocoaExtensions/Custom/Views/CustomCollectionViewCell.swift +++ b/Sources/CocoaExtensions/Custom/Views/CustomCollectionViewCell.swift @@ -7,30 +7,37 @@ open class CustomCollectionViewCell: CocoaCollectionViewCell, CustomCocoaViewControllerProtocol { - @Handler + private(set) open var isVisible = false + + @Handler1 public var onDismiss - @Handler + @Handler1 public var onViewDidLoad - @Handler + @Handler1 public var onViewWillAppear - @Handler + @Handler1 public var onViewDidAppear - @Handler + @Handler1 public var onViewWillDisappear - @Handler + @Handler1 public var onViewDidDisappear - @Handler + @Handler1 public var onViewWillLayout - @Handler + @Handler1 public var onViewDidLayout + open override func loadView() { + guard !tryLoadCustomContentView() else { return } + super.loadView() + } + open override func viewDidLoad() { super.viewDidLoad() _onViewDidLoad() @@ -42,6 +49,7 @@ open class CustomCollectionViewCell: } open override func viewDidAppear() { + isVisible = true super.viewDidAppear() _onViewDidAppear() } @@ -54,6 +62,7 @@ open class CustomCollectionViewCell: open override func viewDidDisappear() { super.viewDidDisappear() _onViewDidDisappear() + isVisible = true } open override func viewWillLayout() { diff --git a/Sources/CocoaExtensions/Extensions/CocoaApplication+.swift b/Sources/CocoaExtensions/Extensions/CocoaApplication+.swift index 648a18d..2818620 100644 --- a/Sources/CocoaExtensions/Extensions/CocoaApplication+.swift +++ b/Sources/CocoaExtensions/Extensions/CocoaApplication+.swift @@ -3,20 +3,20 @@ import FoundationExtensions #if os(macOS) extension CocoaApplication { - public var firstKeyWindow: CocoaWindow? { - windows.first(where: { $0.isKeyWindow }) - } + public var firstKeyWindow: CocoaWindow? { + windows.first(where: { $0.isKeyWindow }) + } } #elseif !os(watchOS) extension UIApplication { - var keyWindow: UIWindow? { - return connectedScenes - .filter { $0.activationState == .foregroundActive } - .first { $0 is UIWindowScene } - .flatMap { $0 as? UIWindowScene } - .map(\.windows) - .flatMap { $0.first(where: \.isKeyWindow) } - .or(windows.first(where: \.isKeyWindow)) - } + var keyWindow: UIWindow? { + return connectedScenes + .filter { $0.activationState == .foregroundActive } + .first { $0 is UIWindowScene } + .flatMap { $0 as? UIWindowScene } + .map(\.windows) + .flatMap { $0.first(where: \.isKeyWindow) } + .or(windows.first(where: \.isKeyWindow)) + } } #endif diff --git a/Sources/CocoaExtensions/Extensions/CocoaReuse.swift b/Sources/CocoaExtensions/Extensions/CocoaReuse.swift index 14a4561..ad982e5 100644 --- a/Sources/CocoaExtensions/Extensions/CocoaReuse.swift +++ b/Sources/CocoaExtensions/Extensions/CocoaReuse.swift @@ -1,172 +1,178 @@ import CocoaAliases public protocol SupplimentaryItemKindProvidingType { - static var supplimentaryItemKind: String { get } + static var supplimentaryItemKind: String { get } } public protocol ReuseIDProvidingType { - static var reuseID: String { get } + static var reuseID: String { get } } extension ReuseIDProvidingType { - public static var reuseID: String { .init(reflecting: self) } + public static var reuseID: String { .init(reflecting: self) } } #if !os(watchOS) extension CocoaCollectionReusableView: ReuseIDProvidingType {} + +#if !os(iOS) && !os(tvOS) // handled by CocoaCollectionReusableView extension CocoaCollectionViewCell: ReuseIDProvidingType {} +#endif + +#if !os(macOS) // handled by CocoaCollectionViewCell extension CocoaTableViewCell: ReuseIDProvidingType {} #endif +#endif #if !os(watchOS) extension CocoaTableView { - #if canImport(UIKit) - public func register< - Cell: CocoaTableViewCell - >( - _ type: Cell.Type - ) { - register( - type, - forCellReuseIdentifier: Cell.reuseID - ) - } - #endif - - public func dequeueReusableCell< - Cell: CocoaTableViewCell - >( - _ type: Cell.Type, - at indexPath: IndexPath - ) -> Cell! { - #if canImport(UIKit) - dequeueReusableCell( - withIdentifier: Cell.reuseID, - for: indexPath - ) as? Cell - #else - makeView( - withIdentifier: NSUserInterfaceItemIdentifier( - rawValue: Cell.reuseID - ), - owner: self - ) as? Cell - #endif - } + #if canImport(UIKit) + public func register< + Cell: CocoaTableViewCell + >( + _ type: Cell.Type + ) { + register( + type, + forCellReuseIdentifier: Cell.reuseID + ) + } + #endif + + public func dequeueReusableCell< + Cell: CocoaTableViewCell + >( + _ type: Cell.Type, + at indexPath: IndexPath + ) -> Cell! { + #if canImport(UIKit) + dequeueReusableCell( + withIdentifier: Cell.reuseID, + for: indexPath + ) as? Cell + #else + makeView( + withIdentifier: NSUserInterfaceItemIdentifier( + rawValue: Cell.reuseID + ), + owner: self + ) as? Cell + #endif + } } #endif #if !os(watchOS) extension CocoaCollectionView { - public func registerSupplimentaryItem< - SupplimentaryItem: - CocoaCollectionReusableView - >( - _ type: SupplimentaryItem.Type?, - ofKind kind: String - ) { - #if canImport(UIKit) - register( - type, - forSupplementaryViewOfKind: kind, - withReuseIdentifier: SupplimentaryItem.reuseID - ) - #elseif canImport(AppKit) - register( - type, - forSupplementaryViewOfKind: kind, - withIdentifier: NSUserInterfaceItemIdentifier( - rawValue: SupplimentaryItem.reuseID - ) - ) - #endif - } - - public func registerSupplimentaryItem< - SupplimentaryItem: - CocoaCollectionReusableView & SupplimentaryItemKindProvidingType - >( - _ type: SupplimentaryItem.Type? - ) { - registerSupplimentaryItem( - type, - ofKind: SupplimentaryItem.supplimentaryItemKind - ) - } - - public func register< - Cell: CocoaCollectionViewCell - >( - _ type: Cell.Type? - ) { - #if canImport(UIKit) - register(type, forCellWithReuseIdentifier: Cell.reuseID) - #elseif canImport(AppKit) - register( - type, - forItemWithIdentifier: NSUserInterfaceItemIdentifier( - Cell.reuseID - ) - ) - #endif - } - - public func dequeueSupplimentaryItem< - SupplimentaryItem: ReuseIDProvidingType - >( - _ type: SupplimentaryItem.Type = SupplimentaryItem.self, - ofKind kind: String, - at indexPath: IndexPath - ) -> SupplimentaryItem! { - #if canImport(UIKit) - dequeueReusableSupplementaryView( - ofKind: kind, - withReuseIdentifier: SupplimentaryItem.reuseID, - for: indexPath - ) as? SupplimentaryItem - #else - makeSupplementaryView( - ofKind: kind, - withIdentifier: NSUserInterfaceItemIdentifier( - rawValue: SupplimentaryItem.reuseID - ), - for: indexPath - ) as? SupplimentaryItem - #endif - } - - public func dequeueSupplimentaryItem< - SupplimentaryItem: CocoaCollectionReusableView & SupplimentaryItemKindProvidingType - >( - _ type: SupplimentaryItem.Type, - at indexPath: IndexPath - ) -> SupplimentaryItem! { - dequeueSupplimentaryItem( - type, - ofKind: type.supplimentaryItemKind, - at: indexPath - ) - } - - public func dequeueReusableCell< - Cell: CocoaCollectionViewCell - >( - _ type: Cell.Type = Cell.self, - at indexPath: IndexPath - ) -> Cell! { - #if canImport(UIKit) - dequeueReusableCell( - withReuseIdentifier: Cell.reuseID, - for: indexPath - ) as? Cell - #else - makeItem( - withIdentifier: NSUserInterfaceItemIdentifier( - rawValue: Cell.reuseID - ), - for: indexPath - ) as? Cell - #endif - } + public func registerSupplimentaryItem< + SupplimentaryItem: + CocoaCollectionReusableView + >( + _ type: SupplimentaryItem.Type?, + ofKind kind: String + ) { + #if canImport(UIKit) + register( + type, + forSupplementaryViewOfKind: kind, + withReuseIdentifier: SupplimentaryItem.reuseID + ) + #elseif canImport(AppKit) + register( + type, + forSupplementaryViewOfKind: kind, + withIdentifier: NSUserInterfaceItemIdentifier( + rawValue: SupplimentaryItem.reuseID + ) + ) + #endif + } + + public func registerSupplimentaryItem< + SupplimentaryItem: + CocoaCollectionReusableView & SupplimentaryItemKindProvidingType + >( + _ type: SupplimentaryItem.Type? + ) { + registerSupplimentaryItem( + type, + ofKind: SupplimentaryItem.supplimentaryItemKind + ) + } + + public func register< + Cell: CocoaCollectionViewCell + >( + _ type: Cell.Type? + ) { + #if canImport(UIKit) + register(type, forCellWithReuseIdentifier: Cell.reuseID) + #elseif canImport(AppKit) + register( + type, + forItemWithIdentifier: NSUserInterfaceItemIdentifier( + Cell.reuseID + ) + ) + #endif + } + + public func dequeueSupplimentaryItem< + SupplimentaryItem: ReuseIDProvidingType + >( + _ type: SupplimentaryItem.Type = SupplimentaryItem.self, + ofKind kind: String, + at indexPath: IndexPath + ) -> SupplimentaryItem! { + #if canImport(UIKit) + dequeueReusableSupplementaryView( + ofKind: kind, + withReuseIdentifier: SupplimentaryItem.reuseID, + for: indexPath + ) as? SupplimentaryItem + #else + makeSupplementaryView( + ofKind: kind, + withIdentifier: NSUserInterfaceItemIdentifier( + rawValue: SupplimentaryItem.reuseID + ), + for: indexPath + ) as? SupplimentaryItem + #endif + } + + public func dequeueSupplimentaryItem< + SupplimentaryItem: CocoaCollectionReusableView & SupplimentaryItemKindProvidingType + >( + _ type: SupplimentaryItem.Type, + at indexPath: IndexPath + ) -> SupplimentaryItem! { + dequeueSupplimentaryItem( + type, + ofKind: type.supplimentaryItemKind, + at: indexPath + ) + } + + public func dequeueReusableCell< + Cell: CocoaCollectionViewCell + >( + _ type: Cell.Type = Cell.self, + at indexPath: IndexPath + ) -> Cell! { + #if canImport(UIKit) + dequeueReusableCell( + withReuseIdentifier: Cell.reuseID, + for: indexPath + ) as? Cell + #else + makeItem( + withIdentifier: NSUserInterfaceItemIdentifier( + rawValue: Cell.reuseID + ), + for: indexPath + ) as? Cell + #endif + } } #endif diff --git a/Sources/CocoaExtensions/Extensions/CompositionalLayout.swift b/Sources/CocoaExtensions/Extensions/CompositionalLayout.swift index 3e8b84b..e1138d7 100644 --- a/Sources/CocoaExtensions/Extensions/CompositionalLayout.swift +++ b/Sources/CocoaExtensions/Extensions/CompositionalLayout.swift @@ -2,16 +2,16 @@ import CocoaAliases extension CocoaCollectionViewLayout { - public static func compositional( - _ layout: CocoaCollectionViewCompositionalLayout - ) -> CocoaCollectionViewLayout { - return layout - } - - public static func flow( - _ layout: CocoaCollectionViewFlowLayout - ) -> CocoaCollectionViewLayout { - return layout - } + public static func compositional( + _ layout: CocoaCollectionViewCompositionalLayout + ) -> CocoaCollectionViewLayout { + return layout + } + + public static func flow( + _ layout: CocoaCollectionViewFlowLayout + ) -> CocoaCollectionViewLayout { + return layout + } } #endif diff --git a/Sources/CocoaExtensions/Extensions/CoreGraphics/CGPoint+.swift b/Sources/CocoaExtensions/Extensions/CoreGraphics/CGPoint+.swift index 012cd2d..da827eb 100644 --- a/Sources/CocoaExtensions/Extensions/CoreGraphics/CGPoint+.swift +++ b/Sources/CocoaExtensions/Extensions/CoreGraphics/CGPoint+.swift @@ -1,8 +1,8 @@ import CoreGraphics extension CGPoint { - public init(_ offset: CGSize) { - self.init(x: offset.width, y: offset.height) - } + public init(_ offset: CGSize) { + self.init(x: offset.width, y: offset.height) + } } diff --git a/Sources/CocoaExtensions/Extensions/CoreGraphics/CGSize+.swift b/Sources/CocoaExtensions/Extensions/CoreGraphics/CGSize+.swift index b2c7757..8f0d544 100644 --- a/Sources/CocoaExtensions/Extensions/CoreGraphics/CGSize+.swift +++ b/Sources/CocoaExtensions/Extensions/CoreGraphics/CGSize+.swift @@ -1,15 +1,15 @@ import CoreGraphics extension CGSize { - public init(_ point: CGPoint) { - self.init(width: point.x, height: point.y) - } - - public static func square(_ length: CGFloat) -> CGSize { - self.init(width: length, height: length) - } - - public var center: CGPoint { - CGPoint(x: width / 2, y: height / 2) - } + public init(_ point: CGPoint) { + self.init(width: point.x, height: point.y) + } + + public static func square(_ length: CGFloat) -> CGSize { + self.init(width: length, height: length) + } + + public var center: CGPoint { + CGPoint(x: width / 2, y: height / 2) + } } diff --git a/Sources/CocoaExtensions/External/Responder++.swift b/Sources/CocoaExtensions/External/Responder++.swift index 340e04f..0eb6f85 100644 --- a/Sources/CocoaExtensions/External/Responder++.swift +++ b/Sources/CocoaExtensions/External/Responder++.swift @@ -8,96 +8,96 @@ import UIKit import FoundationExtensions extension UIResponder { - var globalFrame: CGRect? { - guard let view = self as? UIView else { - return nil - } - - return view.superview?.convert(view.frame, to: nil) - } + var globalFrame: CGRect? { + guard let view = self as? UIView else { + return nil + } + + return view.superview?.convert(view.frame, to: nil) + } } extension UIResponder { - private static weak var _firstResponder: UIResponder? - - @available(macCatalystApplicationExtension, unavailable) - @available(iOSApplicationExtension, unavailable) - @available(tvOSApplicationExtension, unavailable) - static var firstResponder: UIResponder? { - _firstResponder = nil - - UIApplication.shared.sendAction( - #selector(UIResponder.acquireFirstResponder(_:)), - to: nil, - from: nil, - for: nil - ) - - return _firstResponder - } - - @objc private func acquireFirstResponder(_ sender: Any) { - UIResponder._firstResponder = self - } + private static weak var _firstResponder: UIResponder? + + @available(macCatalystApplicationExtension, unavailable) + @available(iOSApplicationExtension, unavailable) + @available(tvOSApplicationExtension, unavailable) + static var firstResponder: UIResponder? { + _firstResponder = nil + + UIApplication.shared.sendAction( + #selector(UIResponder.acquireFirstResponder(_:)), + to: nil, + from: nil, + for: nil + ) + + return _firstResponder + } + + @objc private func acquireFirstResponder(_ sender: Any) { + UIResponder._firstResponder = self + } } extension UIResponder { - public func nearestResponder( - ofType type: Responder.Type - ) -> Responder? { - guard !self.is(type) - else { return (self as! Responder) } - - return next?.nearestResponder(ofType: type) - } - - private func furthestResponder( - ofType type: Responder.Type, - default _default: Responder? - ) -> Responder? { - return next?.furthestResponder( - ofType: type, - default: self as? Responder - ).or(_default) - } - - public func furthestResponder( - ofType type: Responder.Type - ) -> Responder? { - return furthestResponder( - ofType: type, - default: nil - ) - } - - public func forEach( - ofType type: Responder.Type, - recursive iterator: (Responder) throws -> Void - ) rethrows { - if self.is(type) { - try iterator(self as! Responder) - } - - try next?.forEach(ofType: type, recursive: iterator) - } + public func nearestResponder( + ofType type: Responder.Type + ) -> Responder? { + guard !self.is(type) + else { return (self as! Responder) } + + return next?.nearestResponder(ofType: type) + } + + private func furthestResponder( + ofType type: Responder.Type, + default _default: Responder? + ) -> Responder? { + return next?.furthestResponder( + ofType: type, + default: self as? Responder + ).or(_default) + } + + public func furthestResponder( + ofType type: Responder.Type + ) -> Responder? { + return furthestResponder( + ofType: type, + default: nil + ) + } + + public func forEach( + ofType type: Responder.Type, + recursive iterator: (Responder) throws -> Void + ) rethrows { + if self.is(type) { + try iterator(self as! Responder) + } + + try next?.forEach(ofType: type, recursive: iterator) + } } extension UIResponder { - @objc open var nearestViewController: UIViewController? { - nearestResponder(ofType: UIViewController.self) - } - - @objc open var furthestViewController: UIViewController? { - furthestResponder(ofType: UIViewController.self) - } - - @objc open var nearestNavigationController: UINavigationController? { - nearestResponder(ofType: UINavigationController.self) - } - - @objc open var furthestNavigationController: UINavigationController? { - furthestResponder(ofType: UINavigationController.self) - } + @objc open var nearestViewController: UIViewController? { + nearestResponder(ofType: UIViewController.self) + } + + @objc open var furthestViewController: UIViewController? { + furthestResponder(ofType: UIViewController.self) + } + + @objc open var nearestNavigationController: UINavigationController? { + nearestResponder(ofType: UINavigationController.self) + } + + @objc open var furthestNavigationController: UINavigationController? { + furthestResponder(ofType: UINavigationController.self) + } } #endif diff --git a/Sources/CocoaExtensions/Macros.swift b/Sources/CocoaExtensions/Macros.swift deleted file mode 100644 index d83666d..0000000 --- a/Sources/CocoaExtensions/Macros.swift +++ /dev/null @@ -1,246 +0,0 @@ -// There is a bug in Xcode when the package is imported as a dependency -// https://forums.swift.org/t/xcode-15-beta-no-such-module-error-with-swiftpm-and-macro/65486/3 -#if canImport(CocoaExtensionsMacros) -import CocoaExtensionsMacros - -#if !os(watchOS) -@attached(accessor) -@attached(peer, names: named(loadView)) -public macro CustomView() = #externalMacro( - module: "CocoaExtensionsMacros", - type: "CustomViewMacro" -) -#endif - -#if os(macOS) -@attached(accessor) -@attached(peer, names: named(loadWindow)) -public macro CustomWindow() = #externalMacro( - module: "CocoaExtensionsMacros", - type: "CustomWindowMacro" -) -#endif - -#else -// Use propertyWrappers to emulate macros -// if CocoaExtensionsMacros is not available - -#if !os(watchOS) -import CocoaAliases -import FunctionalClosures -import FoundationExtensions - -/// Allows to access typed view of a viewController via corresponding property -/// -/// > Will be replaced with macro -/// -/// Usage: -/// ``` -/// final class ViewController: CustomCocoaViewController { -/// @CustomView -/// var contentView: ContentView -/// -/// override func viewDidLoad() { -/// super.viewDidLoad() -/// contentView.label.text = "Hello, World" -/// } -/// } -/// -/// extension ViewController { -/// final class ContentView: CustomCocoaView { -/// let label = UILabel { $0 -/// .textAlignment(.center) -/// } -/// -/// override func _init() { -/// addSubview(label) -/// } -/// -/// override func layoutSubviews() { -/// label.frame = bounds -/// } -/// } -/// } -/// ``` -/// -/// Note, that you should subclass `CustomCocoaViewController` and use `contentView` identifier for the variable -/// to enable implicit loading. Otherwise you should implement loadView method yourself. -/// ``` -/// final class ViewController: CocoaViewController { -/// @CustomView -/// var contentView: ContentView -/// override func loadView() { -/// _contentView.load(to: self) -/// } -/// } -/// ``` -@propertyWrapper -public struct CustomView: CustomReflectable { - public static subscript( - _enclosingInstance controller: Controller, - wrapped wrappedKeyPath: ReferenceWritableKeyPath, - storage storageKeyPath: ReferenceWritableKeyPath - ) -> ContentView { - get { - guard let contentView = controller[keyPath: \.view] as? ContentView else { - assertionFailure(""" - `\(String(reflecting: Controller.self))` should inherit from CustomCocoaViewController \ - to load view using `@CustomView` property wrapper - """) - - return controller[keyPath: storageKeyPath].load(to: controller) - } - return contentView - } - set { - controller[keyPath: storageKeyPath].load(newValue, to: controller) - } - } - - public var customMirror: Mirror { - Mirror(self, children: [("loadView", { load(to: $0) })]) - } - - public init() { - self.init(wrappedValue: ContentView()) - } - - public init(wrappedValue contentView: ContentView!) { - self.contentView = { contentView } - } - - private var contentView: () -> ContentView? - - @available(*, unavailable, message: "@CustomView can only be applied to classes") - public var wrappedValue: ContentView! { - get { fatalError() } - set { fatalError() } - } - - @discardableResult - public func load( - to controller: CocoaViewController - ) -> ContentView { - return load(contentView()!, to: controller) - } - - @discardableResult - public func load( - _ contentView: ContentView, - to controller: CocoaViewController - ) -> ContentView { - controller.view = contentView - return contentView - } -} - -extension CocoaViewController { - /// Loads `@CustomView var contentView` to the controller - /// - Returns: `true` if loading succeed, `false` if failed - @_spi(Internals) - @discardableResult - public func tryLoadCustomContentView() -> Bool { - let selfMirror = Mirror(reflecting: self) - guard - let _contentView = selfMirror.children - .first(where: { $0.label == "_contentView" })?.value, - let loadView = Mirror(reflecting: _contentView).children - .first(where: { $0.label == "loadView" })?.value, - let loadViewTo = loadView as? ((CocoaViewController) -> Void) - else { return false } - loadViewTo(self) - return true - } -} -#endif - -#if os(macOS) -import CocoaAliases - -@propertyWrapper -public struct CustomWindow: CustomReflectable { - public static subscript( - _enclosingInstance controller: Controller, - wrapped wrappedKeyPath: ReferenceWritableKeyPath, - storage storageKeyPath: ReferenceWritableKeyPath - ) -> Window { - get { - guard let _window = controller[keyPath: \.window] else { - if !controller.tryLoadCustomWindow() { - assertionFailure("Could not load custom window") - } - - let window = Window() - controller[keyPath: storageKeyPath].load(window, to: controller) - return window - } - - guard let window = _window.as(Window.self) else { - assertionFailure(""" - `\(String(reflecting: Controller.self))` should inherit from CustomCocoaWindowController \ - to load window using `@CustomWindow` property wrapper - """) - - return controller[keyPath: storageKeyPath].load(to: controller) - } - return window - } - set { - controller[keyPath: storageKeyPath].load(newValue, to: controller) - } - } - - public var customMirror: Mirror { - Mirror(self, children: [("loadWindow", { load(to: $0) })]) - } - - - public init() { - self.init(wrappedValue: Window()) - } - - public init(wrappedValue window: Window!) { - self.managedWindow = { window } - } - - private var managedWindow: () -> Window? - - @available(*, unavailable, message: "@CustomWindow can only be applied to classes") - public var wrappedValue: Window! { - get { fatalError() } - set { fatalError() } - } - - @discardableResult - public func load( - to controller: NSWindowController - ) -> Window { - return load(managedWindow()!, to: controller) - } - - @discardableResult - public func load( - _ managedWindow: Window, - to controller: NSWindowController - ) -> Window { - controller.window = managedWindow - return managedWindow - } -} - -extension NSWindowController { - @_spi(Internals) - @discardableResult - public func tryLoadCustomWindow() -> Bool { - guard - let _managedWindow = Mirror(reflecting: self).children.first(where: { $0.label == "_managedWindow" })?.value, - let loadWindow = Mirror(reflecting: _managedWindow).children.first(where: { $0.label == "loadWindow" })?.value, - let loadWindowTo = loadWindow as? ((NSWindowController) -> Void) - else { return false } - loadWindowTo(self) - return true - } -} -#endif - -#endif diff --git a/Sources/CocoaExtensions/PropertyWrappers/CustomViewMacroFallback.swift b/Sources/CocoaExtensions/PropertyWrappers/CustomViewMacroFallback.swift new file mode 100644 index 0000000..2bc80a8 --- /dev/null +++ b/Sources/CocoaExtensions/PropertyWrappers/CustomViewMacroFallback.swift @@ -0,0 +1,130 @@ +#if !os(watchOS) +import CocoaAliases +import FunctionalClosures +import FoundationExtensions + +protocol _CustomViewLoaderProtocol { + func _load(to controller: CocoaViewController) +} + +/// Allows to access typed view of a viewController via corresponding property +/// +/// > Will be replaced with macro +/// +/// Usage: +/// ``` +/// final class ViewController: CustomCocoaViewController { +/// @_CustomView +/// var contentView: ContentView +/// +/// override func viewDidLoad() { +/// super.viewDidLoad() +/// contentView.label.text = "Hello, World" +/// } +/// } +/// +/// extension ViewController { +/// final class ContentView: CustomCocoaView { +/// let label = UILabel { $0 +/// .textAlignment(.center) +/// } +/// +/// override func _init() { +/// addSubview(label) +/// } +/// +/// override func layoutSubviews() { +/// label.frame = bounds +/// } +/// } +/// } +/// ``` +/// +/// Note, that you should subclass `CustomCocoaViewController` and use `contentView` identifier for the variable +/// to enable implicit loading. Otherwise you should implement loadView method yourself. +/// ``` +/// final class ViewController: CocoaViewController { +/// @_CustomView +/// var contentView: ContentView +/// override func loadView() { +/// _contentView.load(to: self) +/// } +/// } +/// ``` +@propertyWrapper +public final class _CustomView: _CustomViewLoaderProtocol { + public static subscript( + _enclosingInstance controller: Controller, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath + ) -> ContentView! { + get { + return controller[keyPath: \.view].as(ContentView.self) ?? { + assertionFailure(""" + `\(String(reflecting: Controller.self))` should inherit from CustomCocoaViewController \ + to load view using `@_CustomView` property wrapper + """) + + return controller[keyPath: storageKeyPath].load(to: controller) + }() + } + set { + controller[keyPath: storageKeyPath].load(newValue, to: controller) + } + } + + public convenience init() { + self.init(wrappedValue: ContentView()) + } + + public init(wrappedValue contentView: ContentView!) { + self.contentView = { contentView } + } + + private var contentView: () -> ContentView? + + @available(*, unavailable, message: "@_CustomView propery wrapper can only be applied to classes") + public var wrappedValue: ContentView! { + get { fatalError() } + set { fatalError() } + } + + func _load(to controller: CocoaViewController) { + self.load(to: controller) + } + + @discardableResult + public func load( + to controller: CocoaViewController + ) -> ContentView { + return load(contentView()!, to: controller) + } + + @discardableResult + public func load( + _ contentView: ContentView, + to controller: CocoaViewController + ) -> ContentView { + controller.view = contentView + return contentView + } +} + +extension CocoaViewController { + /// Loads `@CustomView var contentView` to the controller + /// - Returns: `true` if loading succeed, `false` if failed + @_spi(Internals) + @discardableResult + public func tryLoadCustomContentView() -> Bool { + guard + let _contentView = Mirror(reflecting: self) + .children.reduce(_CustomViewLoaderProtocol?.none, { loader, candidate in + guard loader == nil else { return nil } + return candidate.value as? _CustomViewLoaderProtocol + }) + else { return false } + _contentView._load(to: self) + return true + } +} +#endif diff --git a/Sources/CocoaExtensions/PropertyWrappers/CustomWindowMacroFallback.swift b/Sources/CocoaExtensions/PropertyWrappers/CustomWindowMacroFallback.swift new file mode 100644 index 0000000..361a4de --- /dev/null +++ b/Sources/CocoaExtensions/PropertyWrappers/CustomWindowMacroFallback.swift @@ -0,0 +1,97 @@ +#if os(macOS) +import CocoaAliases +import FunctionalClosures +import FoundationExtensions +import CocoaAliases + +protocol _CustomWindowLoaderProtocol { + func _load(to controller: NSWindowController) +} + +@propertyWrapper +public final class _CustomWindow: _CustomWindowLoaderProtocol { + public static subscript( + _enclosingInstance controller: Controller, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath + ) -> Window! { + get { + let _window = controller[keyPath: \.window] ?? { + if !controller.tryLoadCustomWindow() { + assertionFailure("Could not load custom window") + } + + let window = Window() + controller[keyPath: storageKeyPath].load(window, to: controller) + return window + }() + + let window = _window.as(Window.self) ?? { + assertionFailure(""" + `\(String(reflecting: Controller.self))` should inherit from CustomCocoaWindowController \ + to load window using `@CustomWindow` property wrapper + """) + + return controller[keyPath: storageKeyPath].load(to: controller) + }() + + return window + } + set { + controller[keyPath: storageKeyPath].load(newValue, to: controller) + } + } + + public convenience init() { + self.init(wrappedValue: Window()) + } + + public init(wrappedValue window: Window!) { + self.managedWindow = { window } + } + + private var managedWindow: () -> Window? + + @available(*, unavailable, message: "@CustomWindow can only be applied to classes") + public var wrappedValue: Window! { + get { fatalError() } + set { fatalError() } + } + + func _load(to controller: NSWindowController) { + self.load(to: controller) + } + + @discardableResult + public func load( + to controller: NSWindowController + ) -> Window { + return load(managedWindow()!, to: controller) + } + + @discardableResult + public func load( + _ managedWindow: Window, + to controller: NSWindowController + ) -> Window { + controller.window = managedWindow + return managedWindow + } +} + +extension NSWindowController { + @_spi(Internals) + @discardableResult + public func tryLoadCustomWindow() -> Bool { + guard + let _managedWindow = Mirror(reflecting: self) + .children.reduce(_CustomWindowLoaderProtocol?.none, { loader, candidate in + guard loader == nil else { return nil } + return candidate.value as? _CustomWindowLoaderProtocol + }) + else { return false } + _managedWindow._load(to: self) + return true + } +} +#endif diff --git a/Sources/CocoaExtensions/SwiftUI/CocoaComponent.swift b/Sources/CocoaExtensions/SwiftUI/CocoaComponent.swift index 8cc71c6..b9cc657 100644 --- a/Sources/CocoaExtensions/SwiftUI/CocoaComponent.swift +++ b/Sources/CocoaExtensions/SwiftUI/CocoaComponent.swift @@ -3,168 +3,247 @@ import SwiftUI import CocoaAliases public struct CocoaComponent: View { - internal let content: () -> Representable - public var body: some View { content() } + @_spi(Internals) + public let content: Representable + + @_spi(Internals) + public init(content: () -> Representable) { + self.content = content() + } + + public var body: some View { content } } public struct _CocoaViewRepresentable: CocoaViewRepresentable { - internal let content: (Context) -> Content - internal let update: (Content, Context) -> Void - internal let coordinator: () -> Coordinator - - public func makeCocoaView(context: Context) -> Content { - content(context) - } - - public func updateCocoaView(_ content: Content, context: Context) { - update(content, context) - } - - public func makeCoordinator() -> Coordinator { - coordinator() - } + @_spi(Internals) + public let content: (Context) -> Content + + @_spi(Internals) + public let update: (Content, Context) -> Void + + @_spi(Internals) + public let coordinator: () -> Coordinator + + @_spi(Internals) + public init( + content: @escaping (Context) -> Content, + update: @escaping (Content, Context) -> Void, + coordinator: @escaping () -> Coordinator + ) { + self.content = content + self.update = update + self.coordinator = coordinator + } + + public func makeCocoaView(context: Context) -> Content { + content(context) + } + + public func updateCocoaView(_ content: Content, context: Context) { + update(content, context) + } + + public func makeCoordinator() -> Coordinator { + coordinator() + } } public struct _CocoaViewControllerRepresentable: CocoaViewControllerRepresentable { - internal let content: (Context) -> Content - internal let update: (Content, Context) -> Void - internal let coordinator: () -> Coordinator - - public func makeCocoaViewController(context: Context) -> Content { - content(context) - } - - public func updateCocoaViewController(_ content: Content, context: Context) { - update(content, context) - } - - public func makeCoordinator() -> Coordinator { - coordinator() - } + @_spi(Internals) + public let content: (Context) -> Content + + @_spi(Internals) + public let update: (Content, Context) -> Void + + @_spi(Internals) + public let coordinator: () -> Coordinator + + @_spi(Internals) + public init( + content: @escaping (Context) -> Content, + update: @escaping (Content, Context) -> Void, + coordinator: @escaping () -> Coordinator + ) { + self.content = content + self.update = update + self.coordinator = coordinator + } + + public func makeCocoaViewController(context: Context) -> Content { + content(context) + } + + public func updateCocoaViewController(_ content: Content, context: Context) { + update(content, context) + } + + public func makeCoordinator() -> Coordinator { + coordinator() + } } extension CocoaComponent { - public init< - Content: CocoaView, - Coordinator - >( - content: @escaping (Representable.Context) -> Content, - update: @escaping (Content, Representable.Context) -> Void, - coordinator: @escaping () -> Coordinator - ) where Representable == _CocoaViewRepresentable { - self.init(content: { - Representable( - content: content, - update: update, - coordinator: coordinator - ) - }) - } - - public init< - Content: CocoaView, - Coordinator - >( - _ content: @escaping @autoclosure () -> Content, - update: @escaping (Content, Representable.Context) -> Void, - coordinator: @escaping () -> Coordinator - ) where Representable == _CocoaViewRepresentable { - self.init(content: { - Representable( - content: { _ in content() }, - update: update, - coordinator: coordinator - ) - }) - } - - public init( - content: @escaping (Representable.Context) -> Content, - update: @escaping (Content, Representable.Context) -> Void = { _, _ in } - ) where Representable == _CocoaViewRepresentable { - self.init(content: { - Representable( - content: content, - update: update, - coordinator: {} - ) - }) - } - - public init( - _ content: @escaping @autoclosure () -> Content, - update: @escaping (Content, Representable.Context) -> Void = { _, _ in } - ) where Representable == _CocoaViewRepresentable { - self.init(content: { - Representable( - content: { _ in content() }, - update: update, - coordinator: {} - ) - }) - } + public init< + Content: CocoaView, + Coordinator + >( + content: @escaping (Representable.Context) -> Content, + update: @escaping (Content, Representable.Context) -> Void, + coordinator: @escaping () -> Coordinator + ) where Representable == _CocoaViewRepresentable { + self.init(content: { + Representable( + content: content, + update: update, + coordinator: coordinator + ) + }) + } + + public init< + Content: CocoaView, + Coordinator + >( + content: @escaping () -> Content, + update: @escaping (Content, Representable.Context) -> Void, + coordinator: @escaping () -> Coordinator + ) where Representable == _CocoaViewRepresentable { + self.init( + content: { _ in content() }, + update: update, + coordinator: coordinator + ) + } + + public init< + Content: CocoaView, + Coordinator + >( + _ content: @escaping @autoclosure () -> Content, + update: @escaping (Content, Representable.Context) -> Void, + coordinator: @escaping () -> Coordinator + ) where Representable == _CocoaViewRepresentable { + self.init( + content: content, + update: update, + coordinator: coordinator + ) + } + + public init( + content: @escaping (Representable.Context) -> Content, + update: @escaping (Content, Representable.Context) -> Void = { _, _ in } + ) where Representable == _CocoaViewRepresentable { + self.init( + content: content, + update: update, + coordinator: {} + ) + } + + public init( + content: @escaping () -> Content, + update: @escaping (Content, Representable.Context) -> Void = { _, _ in } + ) where Representable == _CocoaViewRepresentable { + self.init( + content: content, + update: update, + coordinator: {} + ) + } + + public init( + _ content: @escaping @autoclosure () -> Content, + update: @escaping (Content, Representable.Context) -> Void = { _, _ in } + ) where Representable == _CocoaViewRepresentable { + self.init( + content: content, + update: update, + coordinator: {} + ) + } } - + extension CocoaComponent { - public init< - Content: CocoaViewController, - Coordinator - >( - content: @escaping (Representable.Context) -> Content, - update: @escaping (Content, Representable.Context) -> Void, - coordinator: @escaping () -> Coordinator - ) where Representable == _CocoaViewControllerRepresentable { - self.init(content: { - Representable( - content: content, - update: update, - coordinator: coordinator - ) - }) - } - - public init< - Content: CocoaViewController, - Coordinator - >( - _ content: @escaping @autoclosure () -> Content, - update: @escaping (Content, Representable.Context) -> Void, - coordinator: @escaping () -> Coordinator - ) where Representable == _CocoaViewControllerRepresentable { - self.init(content: { - Representable( - content: { _ in content() }, - update: update, - coordinator: coordinator - ) - }) - } - - public init( - content: @escaping (Representable.Context) -> Content, - update: @escaping (Content, Representable.Context) -> Void = { _, _ in } - ) where Representable == _CocoaViewControllerRepresentable { - self.init(content: { - Representable( - content: content, - update: update, - coordinator: {} - ) - }) - } - - public init( - _ content: @escaping @autoclosure () -> Content, - update: @escaping (Content, Representable.Context) -> Void = { _, _ in } - ) where Representable == _CocoaViewControllerRepresentable { - self.init(content: { - Representable( - content: { _ in content() }, - update: update, - coordinator: {} - ) - }) - } + public init< + Content: CocoaViewController, + Coordinator + >( + content: @escaping (Representable.Context) -> Content, + update: @escaping (Content, Representable.Context) -> Void, + coordinator: @escaping () -> Coordinator + ) where Representable == _CocoaViewControllerRepresentable { + self.init(content: { + Representable( + content: content, + update: update, + coordinator: coordinator + ) + }) + } + + public init< + Content: CocoaViewController, + Coordinator + >( + content: @escaping () -> Content, + update: @escaping (Content, Representable.Context) -> Void = { _, _ in }, + coordinator: @escaping () -> Coordinator + ) where Representable == _CocoaViewControllerRepresentable { + self.init( + content: { _ in content() }, + update: update, + coordinator: {} + ) + } + + public init< + Content: CocoaViewController, + Coordinator + >( + _ content: @escaping @autoclosure () -> Content, + update: @escaping (Content, Representable.Context) -> Void, + coordinator: @escaping () -> Coordinator + ) where Representable == _CocoaViewControllerRepresentable { + self.init( + content: { _ in content() }, + update: update, + coordinator: coordinator + ) + } + + public init( + content: @escaping (Representable.Context) -> Content, + update: @escaping (Content, Representable.Context) -> Void = { _, _ in } + ) where Representable == _CocoaViewControllerRepresentable { + self.init( + content: content, + update: update, + coordinator: {} + ) + } + + public init( + _ content: @escaping () -> Content, + update: @escaping (Content, Representable.Context) -> Void = { _, _ in } + ) where Representable == _CocoaViewControllerRepresentable { + self.init( + content: content, + update: update, + coordinator: {} + ) + } + + public init( + _ content: @escaping @autoclosure () -> Content, + update: @escaping (Content, Representable.Context) -> Void = { _, _ in } + ) where Representable == _CocoaViewControllerRepresentable { + self.init( + content: content, + update: update, + coordinator: {} + ) + } } #endif diff --git a/Sources/CocoaExtensions/SwiftUI/HostingView.swift b/Sources/CocoaExtensions/SwiftUI/HostingView.swift index de63242..dc6a565 100644 --- a/Sources/CocoaExtensions/SwiftUI/HostingView.swift +++ b/Sources/CocoaExtensions/SwiftUI/HostingView.swift @@ -2,63 +2,63 @@ import SwiftUI open class UIHostingView: CustomCocoaView { - public let controller: UIHostingController - - public convenience init(@ViewBuilder content: () -> RootView) { - self.init(rootView: content()) - } - - public init(rootView: RootView) { - self.controller = .init(rootView: rootView) - super.init(frame: .zero) - } - - public override init(frame: CGRect) { - guard let rootView = Self.tryInitOptionalRootView() - else { fatalError("Root view is not expressible by nil literal") } - self.controller = UIHostingController(rootView: rootView) - super.init(frame: frame) - } - - public required init?(coder: NSCoder) { - guard let rootView = Self.tryInitOptionalRootView() - else { fatalError("Root view is not expressible by nil literal") } - self.controller = UIHostingController(rootView: rootView) - super.init(coder: coder) - } - - public var rootView: RootView { - get { controller.rootView } - set { controller.rootView = newValue } - } - - open override func _init() { - super._init() - self.backgroundColor = .clear - self.controller.view.backgroundColor = .clear - self.addSubview(controller.view) - } - - open override func layoutSubviews() { - controller.view.frame = bounds - controller.view.setNeedsLayout() - } - - open override func didMoveToSuperview() { - super.didMoveToSuperview() - nearestViewController.map { parent in - controller.didMove(toParent: parent) - } - } + public let controller: UIHostingController + + public convenience init(@ViewBuilder content: () -> RootView) { + self.init(rootView: content()) + } + + public init(rootView: RootView) { + self.controller = .init(rootView: rootView) + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + guard let rootView = Self.tryInitOptionalRootView() + else { fatalError("Root view is not expressible by nil literal") } + self.controller = UIHostingController(rootView: rootView) + super.init(frame: frame) + } + + public required init?(coder: NSCoder) { + guard let rootView = Self.tryInitOptionalRootView() + else { fatalError("Root view is not expressible by nil literal") } + self.controller = UIHostingController(rootView: rootView) + super.init(coder: coder) + } + + public var rootView: RootView { + get { controller.rootView } + set { controller.rootView = newValue } + } + + open override func _init() { + super._init() + self.backgroundColor = .clear + self.controller.view.backgroundColor = .clear + self.addSubview(controller.view) + } + + open override func layoutSubviews() { + controller.view.frame = bounds + controller.view.setNeedsLayout() + } + + open override func didMoveToSuperview() { + super.didMoveToSuperview() + nearestViewController.map { parent in + controller.didMove(toParent: parent) + } + } } extension UIHostingView { - fileprivate static func tryInitOptionalRootView() -> RootView? { - guard - let rootViewType = RootView.self as? ExpressibleByNilLiteral.Type, - let rootView = rootViewType.init(nilLiteral: ()) as? RootView - else { return nil } - return rootView - } + fileprivate static func tryInitOptionalRootView() -> RootView? { + guard + let rootViewType = RootView.self as? ExpressibleByNilLiteral.Type, + let rootView = rootViewType.init(nilLiteral: ()) as? RootView + else { return nil } + return rootView + } } #endif diff --git a/Sources/CocoaExtensionsMacros/Exports.swift b/Sources/CocoaExtensionsMacros/Exports.swift new file mode 100644 index 0000000..95fc0ed --- /dev/null +++ b/Sources/CocoaExtensionsMacros/Exports.swift @@ -0,0 +1,2 @@ +@_exported import CocoaExtensions +@_exported import FoundationExtensionsMacros diff --git a/Sources/CocoaExtensionsMacros/Helpers/Diagnostics+.swift b/Sources/CocoaExtensionsMacros/Helpers/Diagnostics+.swift deleted file mode 100644 index cda1d49..0000000 --- a/Sources/CocoaExtensionsMacros/Helpers/Diagnostics+.swift +++ /dev/null @@ -1,11 +0,0 @@ -import SwiftDiagnostics -import SwiftSyntaxMacros - -extension MacroExpansionContext { - func diagnose(_ diagnostic: Diagnostic, return value: T) -> T { - self.diagnose(diagnostic) - return value - } -} - -extension Diagnostic: Error {} diff --git a/Sources/CocoaExtensionsMacros/Helpers/Operators.swift b/Sources/CocoaExtensionsMacros/Helpers/Operators.swift deleted file mode 100644 index cf90001..0000000 --- a/Sources/CocoaExtensionsMacros/Helpers/Operators.swift +++ /dev/null @@ -1,43 +0,0 @@ -func ==( - lhs: KeyPath, - rhs: B -) -> (A) -> Bool { - return { a in - a[keyPath: lhs] == rhs - } -} - -func !=( - lhs: KeyPath, - rhs: B -) -> (A) -> Bool { - return { a in - a[keyPath: lhs] != rhs - } -} - -func ||( - lhs: @escaping (A) -> Bool, - rhs: @escaping (A) -> Bool -) -> (A) -> Bool { - return { a in - lhs(a) || rhs(a) - } -} - -func &&( - lhs: @escaping (A) -> Bool, - rhs: @escaping (A) -> Bool -) -> (A) -> Bool { - return { a in - lhs(a) && rhs(a) - } -} - -prefix func !( - f: @escaping (A) -> Bool -) -> (A) -> Bool { - return { a in - !f(a) - } -} diff --git a/Sources/CocoaExtensionsMacros/Helpers/Result+.swift b/Sources/CocoaExtensionsMacros/Helpers/Result+.swift deleted file mode 100644 index 8b13789..0000000 --- a/Sources/CocoaExtensionsMacros/Helpers/Result+.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Sources/CocoaExtensionsMacros/Macros.swift b/Sources/CocoaExtensionsMacros/Macros.swift new file mode 100644 index 0000000..ec38964 --- /dev/null +++ b/Sources/CocoaExtensionsMacros/Macros.swift @@ -0,0 +1,17 @@ +#if !os(watchOS) +@attached(accessor) +@attached(peer, names: named(loadView)) +public macro CustomView() = #externalMacro( + module: "CocoaExtensionsMacrosPlugin", + type: "CustomViewMacro" +) +#endif + +#if os(macOS) +@attached(accessor) +@attached(peer, names: named(loadWindow)) +public macro CustomWindow() = #externalMacro( + module: "CocoaExtensionsMacrosPlugin", + type: "CustomWindowMacro" +) +#endif diff --git a/Sources/CocoaExtensionsMacros/CustomViewMacro/CustomViewMacro.swift b/Sources/CocoaExtensionsMacrosPlugin/CustomViewMacro/CustomViewMacro.swift similarity index 96% rename from Sources/CocoaExtensionsMacros/CustomViewMacro/CustomViewMacro.swift rename to Sources/CocoaExtensionsMacrosPlugin/CustomViewMacro/CustomViewMacro.swift index d5fccd1..f3d8ae7 100644 --- a/Sources/CocoaExtensionsMacros/CustomViewMacro/CustomViewMacro.swift +++ b/Sources/CocoaExtensionsMacrosPlugin/CustomViewMacro/CustomViewMacro.swift @@ -59,7 +59,7 @@ public struct CustomViewMacro: CustomPropertyMacro { let decl: DeclSyntax = """ \(raw: accessModifier) override func loadView() { - self.view = \(raw: initializer) + self.\(raw: property.name) = \(raw: initializer) } """ diff --git a/Sources/CocoaExtensionsMacros/CustomWindowMacro/CustomWindowMacro.swift b/Sources/CocoaExtensionsMacrosPlugin/CustomWindowMacro/CustomWindowMacro.swift similarity index 96% rename from Sources/CocoaExtensionsMacros/CustomWindowMacro/CustomWindowMacro.swift rename to Sources/CocoaExtensionsMacrosPlugin/CustomWindowMacro/CustomWindowMacro.swift index e431051..c0c990b 100644 --- a/Sources/CocoaExtensionsMacros/CustomWindowMacro/CustomWindowMacro.swift +++ b/Sources/CocoaExtensionsMacrosPlugin/CustomWindowMacro/CustomWindowMacro.swift @@ -59,7 +59,7 @@ public struct CustomWindowMacro: CustomPropertyMacro { let decl: DeclSyntax = """ \(raw: accessModifier) override func loadWindow() { - self.window = \(raw: initializer) + self.\(raw: property.name) = \(raw: initializer) } """ diff --git a/Sources/CocoaExtensionsMacrosPlugin/Helpers/Diagnostics+.swift b/Sources/CocoaExtensionsMacrosPlugin/Helpers/Diagnostics+.swift new file mode 100644 index 0000000..5ee9658 --- /dev/null +++ b/Sources/CocoaExtensionsMacrosPlugin/Helpers/Diagnostics+.swift @@ -0,0 +1,11 @@ +import SwiftDiagnostics +import SwiftSyntaxMacros + +extension MacroExpansionContext { + func diagnose(_ diagnostic: Diagnostic, return value: T) -> T { + self.diagnose(diagnostic) + return value + } +} + +extension Diagnostic: Error {} diff --git a/Sources/CocoaExtensionsMacrosPlugin/Helpers/Operators.swift b/Sources/CocoaExtensionsMacrosPlugin/Helpers/Operators.swift new file mode 100644 index 0000000..5623103 --- /dev/null +++ b/Sources/CocoaExtensionsMacrosPlugin/Helpers/Operators.swift @@ -0,0 +1,43 @@ +func ==( + lhs: KeyPath, + rhs: B +) -> (A) -> Bool { + return { a in + a[keyPath: lhs] == rhs + } +} + +func !=( + lhs: KeyPath, + rhs: B +) -> (A) -> Bool { + return { a in + a[keyPath: lhs] != rhs + } +} + +func ||( + lhs: @escaping (A) -> Bool, + rhs: @escaping (A) -> Bool +) -> (A) -> Bool { + return { a in + lhs(a) || rhs(a) + } +} + +func &&( + lhs: @escaping (A) -> Bool, + rhs: @escaping (A) -> Bool +) -> (A) -> Bool { + return { a in + lhs(a) && rhs(a) + } +} + +prefix func !( + f: @escaping (A) -> Bool +) -> (A) -> Bool { + return { a in + !f(a) + } +} diff --git a/Sources/CocoaExtensionsMacros/Internal/CustomPropertyMacro.swift b/Sources/CocoaExtensionsMacrosPlugin/Internal/CustomPropertyMacro.swift similarity index 100% rename from Sources/CocoaExtensionsMacros/Internal/CustomPropertyMacro.swift rename to Sources/CocoaExtensionsMacrosPlugin/Internal/CustomPropertyMacro.swift diff --git a/Sources/CocoaExtensionsMacros/Internal/Extensions/Type+.swift b/Sources/CocoaExtensionsMacrosPlugin/Internal/Extensions/Type+.swift similarity index 100% rename from Sources/CocoaExtensionsMacros/Internal/Extensions/Type+.swift rename to Sources/CocoaExtensionsMacrosPlugin/Internal/Extensions/Type+.swift diff --git a/Sources/CocoaExtensionsMacros/Plugin.swift b/Sources/CocoaExtensionsMacrosPlugin/Plugin.swift similarity index 100% rename from Sources/CocoaExtensionsMacros/Plugin.swift rename to Sources/CocoaExtensionsMacrosPlugin/Plugin.swift diff --git a/Tests/CocoaExtensionsMacrosPluginTests/CustomViewTests.swift b/Tests/CocoaExtensionsMacrosPluginTests/CustomViewTests.swift new file mode 100644 index 0000000..cc6c8d6 --- /dev/null +++ b/Tests/CocoaExtensionsMacrosPluginTests/CustomViewTests.swift @@ -0,0 +1,191 @@ +import XCTest +import MacroTesting +import CocoaExtensionsMacrosPlugin + +final class CustomViewTests: XCTestCase { + override func invokeTest() { + withMacroTesting( + isRecording: false, + macros: [ + "CustomView": CustomViewMacro.self + ] + ) { + super.invokeTest() + } + } + + func testAttachmentToNonUntypedProperty() { + assertMacro { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView + } + """ + } diagnostics: { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView + ╰─ 🛑 `@CustomView` requires explicit type declaration. + } + """ + } + } + + func testAttachmentToNonOptionalProperty() { + assertMacro { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView: ContentView + } + """ + } diagnostics: { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView: ContentView + ╰─ 🛑 `@CustomView` requires property to be of Optional type + ✏️ Add exclamation mark + } + """ + } fixes: { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView: ContentView! + } + """ + } expansion: { + """ + class ViewController: CustomCocoaViewController { + var contentView: ContentView! { + get { + self.view as? ContentView + } + set { + self.view = newValue + } + } + + public override func loadView() { + self.contentView = ContentView() + } + } + """ + } + } + + func testAttachment() { + assertMacro { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView: ContentView! + } + """ + } expansion: { + """ + class ViewController: CustomCocoaViewController { + var contentView: ContentView! { + get { + self.view as? ContentView + } + set { + self.view = newValue + } + } + + public override func loadView() { + self.contentView = ContentView() + } + } + """ + } + } + + func testAttachmentWithInitialValue() { + assertMacro { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView: ContentView! = .init(fancyInit: true) + } + """ + } expansion: { + """ + class ViewController: CustomCocoaViewController { + var contentView: ContentView! = .init(fancyInit: true) { + get { + self.view as? ContentView + } + set { + self.view = newValue + } + } + + public override func loadView() { + self.contentView = .init(fancyInit: true) + } + } + """ + } + } + + func testAttachmentWithTypedInitialValue() { + assertMacro { + """ + class ViewController: CustomCocoaViewController { + @CustomView + var contentView: ContentView! = ContentView(fancyInit: true) + } + """ + } expansion: { + """ + class ViewController: CustomCocoaViewController { + var contentView: ContentView! = ContentView(fancyInit: true) { + get { + self.view as? ContentView + } + set { + self.view = newValue + } + } + + public override func loadView() { + self.contentView = ContentView(fancyInit: true) + } + } + """ + } + } + + func testOpenAttachment() { + assertMacro { + """ + class ViewController: CustomCocoaViewController { + @CustomView + open var contentView: ContentView! + } + """ + } expansion: { + """ + class ViewController: CustomCocoaViewController { + open var contentView: ContentView! { + get { + self.view as? ContentView + } + set { + self.view = newValue + } + } + + open override func loadView() { + self.contentView = ContentView() + } + } + """ + } + } +} diff --git a/Tests/CocoaExtensionsMacrosPluginTests/CustomWindowTests.swift b/Tests/CocoaExtensionsMacrosPluginTests/CustomWindowTests.swift new file mode 100644 index 0000000..cb0c21f --- /dev/null +++ b/Tests/CocoaExtensionsMacrosPluginTests/CustomWindowTests.swift @@ -0,0 +1,163 @@ +import XCTest +import MacroTesting +import CocoaExtensionsMacrosPlugin + +final class CustomWindowTests: XCTestCase { + override func invokeTest() { + withMacroTesting( + isRecording: false, + macros: [ + "CustomWindow": CustomWindowMacro.self + ] + ) { + super.invokeTest() + } + } + + func testAttachmentToNonUntypedProperty() { + assertMacro { + """ + class WindowController: CustomCocoaWindowController { + @CustomWindow + var managedWindow + } + """ + } diagnostics: { + """ + class WindowController: CustomCocoaWindowController { + @CustomWindow + var managedWindow + ╰─ 🛑 `@CustomWindow` requires explicit type declaration. + } + """ + } + } + + func testAttachmentToNonOptionalProperty() { + assertMacro { + """ + class WindowController { + @CustomWindow + var managedWindow: ContentWindow + } + """ + } diagnostics: { + """ + class WindowController { + @CustomWindow + var managedWindow: ContentWindow + ╰─ 🛑 `@CustomWindow` requires property to be of Optional type + ✏️ Add exclamation mark + } + """ + }fixes: { + """ + class WindowController { + @CustomWindow + var managedWindow: ContentWindow! + } + """ + } expansion: { + """ + class WindowController { + var managedWindow: ContentWindow! { + get { + self.window as? ContentWindow + } + set { + self.window = newValue + } + } + + public override func loadWindow() { + self.managedWindow = ContentWindow() + } + } + """ + } + } + + func testAttachment() { + assertMacro { + """ + class WindowController { + @CustomWindow + var customWindow: ContentWindow! + } + """ + } expansion: { + """ + class WindowController { + var customWindow: ContentWindow! { + get { + self.window as? ContentWindow + } + set { + self.window = newValue + } + } + + public override func loadWindow() { + self.customWindow = ContentWindow() + } + } + """ + } + } + + func testAttachmentWithInitialValue() { + assertMacro { + """ + class WindowController { + @CustomWindow + var customWindow: ContentWindow! = CustomWindow(fancyInit: true) + } + """ + } expansion: { + """ + class WindowController { + var customWindow: ContentWindow! = CustomWindow(fancyInit: true) { + get { + self.window as? ContentWindow + } + set { + self.window = newValue + } + } + + public override func loadWindow() { + self.customWindow = CustomWindow(fancyInit: true) + } + } + """ + } + } + + func testOpenAttachment() { + assertMacro { + """ + class WindowController { + @CustomWindow + open var customWindow: ContentWindow! + } + """ + } expansion: { + """ + class WindowController { + open var customWindow: ContentWindow! { + get { + self.window as? ContentWindow + } + set { + self.window = newValue + } + } + + open override func loadWindow() { + self.customWindow = ContentWindow() + } + } + """ + } + } +} diff --git a/Tests/CocoaExtensionsMacrosTests/CustomViewTests.swift b/Tests/CocoaExtensionsMacrosTests/CustomViewTests.swift index 22f751f..abdb205 100644 --- a/Tests/CocoaExtensionsMacrosTests/CustomViewTests.swift +++ b/Tests/CocoaExtensionsMacrosTests/CustomViewTests.swift @@ -1,163 +1,16 @@ import XCTest -import MacroTesting -import CocoaExtensionsMacros +@testable import CocoaExtensionsMacros +#if !os(watchOS) final class CustomViewTests: XCTestCase { - override func invokeTest() { - withMacroTesting( - isRecording: false, - macros: [ - "CustomView": CustomViewMacro.self - ] - ) { - super.invokeTest() - } - } - - func testAttachmentToNonUntypedProperty() { - assertMacro { - """ - class ViewController: CustomCocoaViewController { - @CustomView - var customView - } - """ - } diagnostics: { - """ - class ViewController: CustomCocoaViewController { - @CustomView - var customView - ╰─ 🛑 `@CustomView` requires explicit type declaration. - } - """ - } - } - - func testAttachmentToNonOptionalProperty() { - assertMacro { - """ - class ViewController: CustomCocoaViewController { - @CustomView - var customView: ContentView - } - """ - } diagnostics: { - """ - class ViewController: CustomCocoaViewController { - @CustomView - var customView: ContentView - ╰─ 🛑 `@CustomView` requires property to be of Optional type - ✏️ Add exclamation mark - } - """ - } fixes: { - """ - class ViewController: CustomCocoaViewController { - @CustomView - var customView: ContentView! - } - """ - }expansion: { - """ - class ViewController: CustomCocoaViewController { - var customView: ContentView! { - get { - self.view as? ContentView - } - set { - self.view = newValue - } - } - - public override func loadView() { - self.view = ContentView() - } - } - """ - } - } - - func testAttachment() { - assertMacro { - """ - class ViewController: CustomCocoaViewController { - @CustomView - var customView: ContentView! - } - """ - } expansion: { - """ - class ViewController: CustomCocoaViewController { - var customView: ContentView! { - get { - self.view as? ContentView - } - set { - self.view = newValue - } - } - - public override func loadView() { - self.view = ContentView() - } - } - """ - } - } - - func testAttachmentWithInitialValue() { - assertMacro { - """ - class ViewController: CustomCocoaViewController { - @CustomView - var customView: ContentView! = CustomView(fancyInit: true) - } - """ - } expansion: { - """ - class ViewController: CustomCocoaViewController { - var customView: ContentView! = CustomView(fancyInit: true) { - get { - self.view as? ContentView - } - set { - self.view = newValue - } - } - - public override func loadView() { - self.view = CustomView(fancyInit: true) - } - } - """ + func testCustomView() throws { + class Controller: CustomCocoaViewController { + @CustomView + var contentView: CustomCocoaView! } - } - func testOpenAttachment() { - assertMacro { - """ - class ViewController: CustomCocoaViewController { - @CustomView - open var customView: ContentView! - } - """ - } expansion: { - """ - class ViewController: CustomCocoaViewController { - open var customView: ContentView! { - get { - self.view as? ContentView - } - set { - self.view = newValue - } - } - - open override func loadView() { - self.view = ContentView() - } - } - """ - } + let controller = Controller() + XCTAssertEqual(controller.view, controller.contentView) } } +#endif diff --git a/Tests/CocoaExtensionsMacrosTests/CustomWindowTests.swift b/Tests/CocoaExtensionsMacrosTests/CustomWindowTests.swift index 6a54258..5025803 100644 --- a/Tests/CocoaExtensionsMacrosTests/CustomWindowTests.swift +++ b/Tests/CocoaExtensionsMacrosTests/CustomWindowTests.swift @@ -1,163 +1,18 @@ import XCTest -import MacroTesting -import CocoaExtensionsMacros +@testable import CocoaExtensionsMacros +#if os(macOS) final class CustomWindowTests: XCTestCase { - override func invokeTest() { - withMacroTesting( - isRecording: false, - macros: [ - "CustomWindow": CustomViewMacro.self - ] - ) { - super.invokeTest() + func testCustomWindow() { + // Should compile + class Controller: CustomCocoaWindowController { + @CustomWindow + var managedWindow: CustomCocoaWindow! } - } - - func testAttachmentToNonUntypedProperty() { - assertMacro { - """ - class WindowController: CustomCocoaWindowController { - @CustomWindow - var customWindow - } - """ - } diagnostics: { - """ - class WindowController: CustomCocoaWindowController { - @CustomWindow - var customWindow - ╰─ 🛑 `@CustomView` requires explicit type declaration. - } - """ - } - } - - func testAttachmentToNonOptionalProperty() { - assertMacro { - """ - class WindowController { - @CustomWindow - var customWindow: ContentWindow - } - """ - } diagnostics: { - """ - class WindowController { - @CustomWindow - var customWindow: ContentWindow - ╰─ 🛑 `@CustomView` requires property to be of Optional type - ✏️ Add exclamation mark - } - """ - } fixes: { - """ - class WindowController { - @CustomWindow - var customWindow: ContentWindow! - } - """ - } expansion: { - """ - class WindowController { - var customWindow: ContentWindow! { - get { - self.view as? ContentWindow - } - set { - self.view = newValue - } - } - - public override func loadView() { - self.view = ContentWindow() - } - } - """ - } - } - - func testAttachment() { - assertMacro { - """ - class WindowController { - @CustomWindow - var customWindow: ContentWindow! - } - """ - } expansion: { - """ - class WindowController { - var customWindow: ContentWindow! { - get { - self.view as? ContentWindow - } - set { - self.view = newValue - } - } - public override func loadView() { - self.view = ContentWindow() - } - } - """ - } - } - - func testAttachmentWithInitialValue() { - assertMacro { - """ - class WindowController { - @CustomWindow - var customWindow: ContentWindow! = CustomWindow(fancyInit: true) - } - """ - } expansion: { - """ - class WindowController { - var customWindow: ContentWindow! = CustomWindow(fancyInit: true) { - get { - self.view as? ContentWindow - } - set { - self.view = newValue - } - } - - public override func loadView() { - self.view = CustomWindow(fancyInit: true) - } - } - """ - } - } - - func testOpenAttachment() { - assertMacro { - """ - class WindowController { - @CustomWindow - open var customWindow: ContentWindow! - } - """ - } expansion: { - """ - class WindowController { - open var customWindow: ContentWindow! { - get { - self.view as? ContentWindow - } - set { - self.view = newValue - } - } - - open override func loadView() { - self.view = ContentWindow() - } - } - """ - } + let controller = Controller() + controller.loadWindow() + XCTAssertEqual(controller.window, controller.managedWindow) } } +#endif diff --git a/Tests/CocoaExtensionsTests/CocoaExtensionsTests.swift b/Tests/CocoaExtensionsTests/CocoaExtensionsTests.swift deleted file mode 100644 index 2afc82d..0000000 --- a/Tests/CocoaExtensionsTests/CocoaExtensionsTests.swift +++ /dev/null @@ -1,22 +0,0 @@ -import XCTest -@testable import CocoaExtensions - -final class CocoaExtensionsTests: XCTestCase { - func testCustomView() throws { - // Should compile - class ImageController: CustomCocoaViewController { - @CustomView - var customView: CustomCocoaView! - } - } - - #if os(macOS) - func testCustomWindow() { - // Should compile - class WindowController: CustomCocoaWindowController { - @CustomWindow - var managedWindow: CustomCocoaWindow! - } - } - #endif -} diff --git a/Tests/CocoaExtensionsTests/CoreGraphicsTests.swift b/Tests/CocoaExtensionsTests/CoreGraphicsTests.swift new file mode 100644 index 0000000..fae3210 --- /dev/null +++ b/Tests/CocoaExtensionsTests/CoreGraphicsTests.swift @@ -0,0 +1,24 @@ +import XCTest +@testable import CocoaExtensions + +final class CoreGraphicsTests: XCTestCase { + func testCGSize_square() { + XCTAssertEqual(CGSize.square(0), CGSize.zero) + XCTAssertEqual(CGSize.square(100), CGSize(width: 100, height: 100)) + } + + func testCGSize_initWithPoint() { + XCTAssertEqual(CGSize(CGPoint.zero), CGSize.zero) + XCTAssertEqual(CGSize(CGPoint(x: 100, y: 100)), CGSize(width: 100, height: 100)) + } + + func testCGPoint_initWithSize() { + XCTAssertEqual(CGPoint(CGSize.zero), CGPoint.zero) + XCTAssertEqual(CGPoint(CGSize(width: 100, height: 100)), CGPoint(x: 100, y: 100)) + } + + func testCGSize_center() { + XCTAssertEqual(CGSize.zero.center, CGPoint.zero) + XCTAssertEqual(CGSize(width: 100, height: 100).center, CGPoint(x: 50, y: 50)) + } +} diff --git a/Tests/CocoaExtensionsTests/CustomViewMacroFallbackTests.swift b/Tests/CocoaExtensionsTests/CustomViewMacroFallbackTests.swift new file mode 100644 index 0000000..2c571d9 --- /dev/null +++ b/Tests/CocoaExtensionsTests/CustomViewMacroFallbackTests.swift @@ -0,0 +1,16 @@ +import XCTest +@testable import CocoaExtensions + +#if !os(watchOS) +final class CustomViewMacroFallbackTests: XCTestCase { + func testCustomView() throws { + class Controller: CustomCocoaViewController { + @_CustomView + var contentView: CustomCocoaView! + } + + let controller = Controller() + XCTAssertEqual(controller.view, controller.contentView) + } +} +#endif diff --git a/Tests/CocoaExtensionsTests/CustomWindowMacroFallbackTests.swift b/Tests/CocoaExtensionsTests/CustomWindowMacroFallbackTests.swift new file mode 100644 index 0000000..d9f3024 --- /dev/null +++ b/Tests/CocoaExtensionsTests/CustomWindowMacroFallbackTests.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import CocoaExtensions + +#if !os(watchOS) +final class CustomWindowMacroFallbackTests: XCTestCase { + func testCustomWindow() { + // Should compile + class Controller: CustomCocoaWindowController { + @_CustomWindow + var managedWindow: CustomCocoaWindow! + } + + let controller = Controller() + controller.loadWindow() + XCTAssertEqual(controller.window, controller.managedWindow) + } +} +#endif