Skip to content

Commit

Permalink
✨ SwiftUI improvements (#149)
Browse files Browse the repository at this point in the history
* ✨ Make lineHeight optional in FontModifier

* ✨ Prepare SwiftUI colors bridge

* 🔥 Ditch static theme

* ♻️ Publish SwiftUIColorsTheme subscript

* ✨ Add ACKHostingController

* ♻️ Make ACKHostingController subclass of UIHostingController

* 📝 Update CHANGELOG
  • Loading branch information
olejnjak authored May 28, 2024
1 parent e207af6 commit 1465515
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 39 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ on:
jobs:
carthage:
name: Carthage
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: AckeeCZ/load-xcode-version@1.1.0
- uses: AckeeCZ/load-xcode-version@v1
- name: Build
run: carthage build --no-skip-current --cache-builds --use-xcframeworks
- uses: actions/cache@v3
Expand All @@ -23,10 +23,10 @@ jobs:
${{ runner.os }}-carthage-
spm:
name: SPM
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: AckeeCZ/load-xcode-version@1.1.0
- uses: AckeeCZ/load-xcode-version@v1
- name: Build
run: swift build -c release
- uses: actions/cache@v3
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ jobs:
# Must be set to this for deploying to GitHub Pages
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: macos-13
runs-on: macos-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v4
- name: Check Xcode version ✅
uses: AckeeCZ/load-xcode-version@1.1.0
uses: AckeeCZ/load-xcode-version@v1
- name: Build DocC
run: |
xcodebuild docbuild -scheme ACKategories \
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ on: [workflow_call]
jobs:
xcodebuild:
name: Xcodebuild
runs-on: macos-13
runs-on: macos-latest
env:
IOS_DEVICE: iPhone 15 Pro Max
steps:
- uses: actions/checkout@v4
- uses: AckeeCZ/load-xcode-version@1.1.0
- uses: AckeeCZ/load-xcode-version@v1
- name: iOS tests
run: set -o pipefail && xcodebuild test -scheme ACKategories -resultBundlePath Tests-iOS.xcresult -sdk iphonesimulator -destination "platform=iOS Simulator,name=$IOS_DEVICE,OS=latest" ONLY_ACTIVE_ARCH=YES | xcpretty
- uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -48,10 +48,10 @@ jobs:
path: Tests-tvOS.xcresult
spm:
name: SPM
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: AckeeCZ/load-xcode-version@1.1.0
- uses: AckeeCZ/load-xcode-version@v1
- name: SPM build
run: swift build
- name: SPM tests
Expand Down
2 changes: 1 addition & 1 deletion .github/xcode-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
15.2
15.4
8 changes: 8 additions & 0 deletions ACKategories.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
6922C77D2AFD1C1A00519CDF /* UINavigationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6922C77C2AFD1C1A00519CDF /* UINavigationControllerTests.swift */; };
6922C7802AFD1C8B00519CDF /* Dummies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69E0A6A02AFD114600C8E8D9 /* Dummies.swift */; };
6922C7812AFD1C8B00519CDF /* FlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69E0A6A12AFD114600C8E8D9 /* FlowCoordinatorTests.swift */; };
69246CFB2BDFE77400AB31A1 /* SwiftUIColorsTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69246CFA2BDFE77400AB31A1 /* SwiftUIColorsTheme.swift */; };
693A92652B3DB290008B3DC3 /* Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 693A925D2B3DB290008B3DC3 /* Networking.framework */; };
693A927B2B3DB2AA008B3DC3 /* RequestAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693A92732B3DB2AA008B3DC3 /* RequestAddress.swift */; };
693A927C2B3DB2AA008B3DC3 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693A92742B3DB2AA008B3DC3 /* APIService.swift */; };
Expand All @@ -33,6 +34,7 @@
693A92A42B3DB394008B3DC3 /* HTTPResponse+TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693A929E2B3DB394008B3DC3 /* HTTPResponse+TestData.swift */; };
693A92A52B3DB394008B3DC3 /* URL+TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693A929F2B3DB394008B3DC3 /* URL+TestData.swift */; };
693A92A62B3DB39F008B3DC3 /* ACKategoriesTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 693A92912B3DB388008B3DC3 /* ACKategoriesTesting.framework */; };
693B39B72BF2359B00DF7C5E /* ACKHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693B39B62BF2359B00DF7C5E /* ACKHostingController.swift */; };
694D14EB2B3DD61A0083E614 /* VersionUpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 694D14E92B3DD6190083E614 /* VersionUpdateManager.swift */; };
694D14EC2B3DD61A0083E614 /* MinBuildNumberFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 694D14EA2B3DD6190083E614 /* MinBuildNumberFetcher.swift */; };
694D14EE2B3DD64C0083E614 /* VersionUpdateFetcher_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 694D14ED2B3DD64C0083E614 /* VersionUpdateFetcher_Mock.swift */; };
Expand Down Expand Up @@ -223,6 +225,7 @@
6922C77C2AFD1C1A00519CDF /* UINavigationControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UINavigationControllerTests.swift; sourceTree = "<group>"; };
6922C77E2AFD1C3300519CDF /* ACKategoriesResponder.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = ACKategoriesResponder.xctestplan; sourceTree = "<group>"; };
6922C77F2AFD1C5000519CDF /* ACKategories.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = ACKategories.xctestplan; sourceTree = "<group>"; };
69246CFA2BDFE77400AB31A1 /* SwiftUIColorsTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUIColorsTheme.swift; sourceTree = "<group>"; };
693A925D2B3DB290008B3DC3 /* Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; };
693A92642B3DB290008B3DC3 /* NetworkingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NetworkingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
693A92732B3DB2AA008B3DC3 /* RequestAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAddress.swift; sourceTree = "<group>"; };
Expand All @@ -244,6 +247,7 @@
693A929D2B3DB394008B3DC3 /* HTTPURLResponse+TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPURLResponse+TestData.swift"; sourceTree = "<group>"; };
693A929E2B3DB394008B3DC3 /* HTTPResponse+TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPResponse+TestData.swift"; sourceTree = "<group>"; };
693A929F2B3DB394008B3DC3 /* URL+TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+TestData.swift"; sourceTree = "<group>"; };
693B39B62BF2359B00DF7C5E /* ACKHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACKHostingController.swift; sourceTree = "<group>"; };
694D14E92B3DD6190083E614 /* VersionUpdateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionUpdateManager.swift; sourceTree = "<group>"; };
694D14EA2B3DD6190083E614 /* MinBuildNumberFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinBuildNumberFetcher.swift; sourceTree = "<group>"; };
694D14ED2B3DD64C0083E614 /* VersionUpdateFetcher_Mock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionUpdateFetcher_Mock.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -647,7 +651,9 @@
69E0A6032AFD10BE00C8E8D9 /* SwiftUIExtensions */ = {
isa = PBXGroup;
children = (
69246CFA2BDFE77400AB31A1 /* SwiftUIColorsTheme.swift */,
69E0A6042AFD10BE00C8E8D9 /* FontModifier.swift */,
693B39B62BF2359B00DF7C5E /* ACKHostingController.swift */,
);
path = SwiftUIExtensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -1264,6 +1270,7 @@
69ACD6DE2AFD133A0021127B /* DateExtensions.swift in Sources */,
69ACD6DF2AFD133A0021127B /* DateFormatting.swift in Sources */,
69ACD6E02AFD133A0021127B /* DictionaryExtensions.swift in Sources */,
69246CFB2BDFE77400AB31A1 /* SwiftUIColorsTheme.swift in Sources */,
69ACD6E12AFD133A0021127B /* ErrorHandlers.swift in Sources */,
69ACD6E32AFD133A0021127B /* IntExtensions.swift in Sources */,
69ACD6E42AFD133A0021127B /* NSAttributedStringExtensions.swift in Sources */,
Expand All @@ -1290,6 +1297,7 @@
69ACD6F62AFD133A0021127B /* UISearchBarExtensions.swift in Sources */,
69ACD6F72AFD133A0021127B /* UIStackViewExtensions.swift in Sources */,
69ACD6F82AFD133A0021127B /* UIView+Spacer.swift in Sources */,
693B39B72BF2359B00DF7C5E /* ACKHostingController.swift in Sources */,
69ACD6F92AFD133A0021127B /* UIViewController+Children.swift in Sources */,
6A72B2222B1A15AC00A59EDD /* BackGesture.swift in Sources */,
69ACD6FA2AFD133A0021127B /* UIViewController+FrontMost.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

## Next

- SwiftUI improvements ([#149](https://github.com/AckeeCZ/ACKategories/pull/149), kudos to @olejnjak)
- make `lineHeight` parameter optional for `FontModifier`
- implement color forwarding from UIKit to SwiftUI
- add `ACKHostingController`

## 6.14.0

- Add privacy manifest ([#148](https://github.com/AckeeCZ/ACKategories/pull/148), kudos to @olejnjak)
Expand Down
1 change: 1 addition & 0 deletions Sources/ACKategories/Base/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extension Base {
}
}

@available(*, unavailable)
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Expand Down
53 changes: 53 additions & 0 deletions Sources/ACKategories/SwiftUIExtensions/ACKHostingController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#if !os(macOS) && !os(watchOS)
import os.log
import SwiftUI

@available(iOS 13.0, tvOS 13.0, *)
open class ACKHostingController<RootView: View>: UIHostingController<RootView> {
/// Navigation bar is shown/hidden in viewWillAppear according to this flag
public var hasNavigationBar = true

#if !os(tvOS)
public override var preferredStatusBarStyle: UIStatusBarStyle {
get { _preferredStatusBarStyle }
set {
_preferredStatusBarStyle = newValue
setNeedsStatusBarAppearanceUpdate()
}
}

private var _preferredStatusBarStyle: UIStatusBarStyle = .default
#endif

// MARK: - Initializers

public override init(rootView: RootView) {
super.init(rootView: rootView)

navigationItem.backButtonTitle = ""

if Base.memoryLoggingEnabled && Base.viewControllerMemoryLoggingEnabled {
os_log("📱 👶 %@", log: Logger.lifecycleLog(), type: .info, self)
}
}

@available(*, unavailable)
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

deinit {
if Base.memoryLoggingEnabled && Base.viewControllerMemoryLoggingEnabled {
os_log("📱 ⚰️ %@", log: Logger.lifecycleLog(), type: .info, self)
}
}

// MARK: - View life cycle

override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

navigationController?.setNavigationBarHidden(!hasNavigationBar, animated: animated)
}
}
#endif
26 changes: 10 additions & 16 deletions Sources/ACKategories/SwiftUIExtensions/FontModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,22 @@ public extension View {
/// - font: The font to use in this view.
/// - lineHeight: The line height to use in this view.
/// - textStyle: The text style for relative font scaling. If `nil`is specified then dynamic type is turned off.
func font(
@ViewBuilder func font(
_ font: UIFont,
lineHeight: Double,
lineHeight: Double?,
textStyle: Font.TextStyle?
) -> some View {
let customFont: Font

// Do not scale font based on dynamic type when nil `relativeTo` specified
if let textStyle = textStyle {
customFont = .custom(
font.fontName,
size: font.pointSize,
relativeTo: textStyle
)
let customFont = textStyle
.map { Font.custom(font.fontName, size: font.pointSize, relativeTo: $0) } ?? Font(font)

if let lineHeight {
self.font(customFont)
.lineSpacing(lineHeight - font.lineHeight)
.padding(.vertical, (lineHeight - font.lineHeight) / 2)
} else {
customFont = Font(font)
self.font(customFont)
}

return self
.font(customFont)
.lineSpacing(lineHeight - font.lineHeight)
.padding(.vertical, (lineHeight - font.lineHeight) / 2)
}
}
#endif
33 changes: 33 additions & 0 deletions Sources/ACKategories/SwiftUIExtensions/SwiftUIColorsTheme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#if canImport(UIKit)
import SwiftUI
import UIKit

/// Using this namespace you can define your colors in ``Theme`` extension of `UIColor`
/// and they will automatically appear in `Color.theme` namespace.
///
/// Make sure your extensions are not defined as static, only instance variables with type `UIColor` will be bridged.
///
/// ## Example
///
/// ```swift
/// extension Theme<UIColor> {
/// var primary: UIColor { .red }
/// }
/// ```
///
/// Then you can use `Color.theme.primary` in SwiftUI
@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *)
@dynamicMemberLookup
public struct SwiftUIColorsTheme {
public subscript(dynamicMember keyPath: KeyPath<Theme<UIColor>, UIColor>) -> SwiftUI.Color {
let uiColor = Theme<UIColor>()[keyPath: keyPath]
return .init(uiColor)
}
}

@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *)
public extension Color {
/// Namespace for bridged ``Theme`` colors from `Theme<UIColor>` extension, see ``SwiftUIColorsTheme``.
static let theme = SwiftUIColorsTheme()
}
#endif
4 changes: 2 additions & 2 deletions Sources/ACKategories/UI/Theme/ThemeProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public struct Theme<Base> { }
public protocol ThemeProvider { }

public extension ThemeProvider {
static var theme: Theme<Self>.Type { Theme<Self>.self }
static var theme: Theme<Self> { Theme<Self>() } // theoretically unneccessary allocation overhead every call, but SnapKit uses the same pattern so...

var theme: Theme<Self> { Theme<Self>() } // theoretically unneccessary allocation overhead every call, but SnapKit uses the same pattern so...
var theme: Theme<Self> { Self.theme }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,10 @@ import ACKategories
import ACKategoriesTesting
import XCTest

@MainActor
@available(tvOS 13.0, iOS 13.0, watchOS 6.0, macOS 10.15, *)
final class VersionUpdateManager_Tests: XCTestCase {
private var fetcher: VersionUpdateFetcher_Mock!

// MARK: - Setup

override func setUp() {
fetcher = .init()
super.setUp()
}

func test_minBuildNumber_lower() async throws {
let fetcher = VersionUpdateFetcher_Mock()
let buildNumber = Int.random(in: 1..<10_000)

fetcher.minBuildNumber = buildNumber - 1
Expand All @@ -29,6 +20,7 @@ final class VersionUpdateManager_Tests: XCTestCase {
}

func test_minBuildNumber_equal() async throws {
let fetcher = VersionUpdateFetcher_Mock()
let buildNumber = Int.random(in: 1..<10_000)

fetcher.minBuildNumber = buildNumber
Expand All @@ -43,6 +35,7 @@ final class VersionUpdateManager_Tests: XCTestCase {
}

func test_minBuildNumber_higher() async throws {
let fetcher = VersionUpdateFetcher_Mock()
let buildNumber = Int.random(in: 1..<10_000)

fetcher.minBuildNumber = buildNumber + 1
Expand Down

0 comments on commit 1465515

Please sign in to comment.