Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/build-examples.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Build Examples

on:
pull_request
on: pull_request

jobs:
build-examples:
Expand All @@ -12,14 +11,19 @@ jobs:
cancel-in-progress: true
strategy:
matrix:
scheme: ['Example (iOS)', 'UIKitApp']
scheme: ["Example (iOS)", "UIKitApp"]

steps:
- uses: actions/checkout@v3

- name: Selecct Xcode 14.1
run: sudo xcode-select -s '/Applications/Xcode_14.1.app/Contents/Developer'

- name: SwiftPM cache
uses: actions/cache@v3
with:
path: SourcePackages
key: ${{ runner.os }}-swiftpm-${{ hashFiles('**/Package.resolved') }}

- name: Build
run: xcodebuild -project Example/Example.xcodeproj -scheme "${{ matrix.scheme }}" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13,OS=latest' -clonedSourcePackagesDirPath SourcePackages
2 changes: 2 additions & 0 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
70A4A52F28251AB3007F2033 /* iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iOS.entitlements; sourceTree = "<group>"; };
70B1D331282C870A000D0386 /* SwiftUI-Common */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "SwiftUI-Common"; path = "../../SwiftUI-Common"; sourceTree = "<group>"; };
70F03A902826266600D86CAB /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; };
70FB86382920D1C500F19842 /* UserDefaultsBrowser */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = UserDefaultsBrowser; path = ..; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -131,6 +132,7 @@
isa = PBXGroup;
children = (
70A4A52E28251A19007F2033 /* UserDefaults-Browser */,
70FB86382920D1C500F19842 /* UserDefaultsBrowser */,
);
name = Packages;
sourceTree = "<group>";
Expand Down
2 changes: 0 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/YusukeHosonuma/SwiftPrettyPrint.git", from: "1.3.0"),
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "0.8.0"),
.package(url: "https://github.com/YusukeHosonuma/SwiftUI-Common.git", from: "1.0.0"),
],
targets: [
.target(name: "UserDefaultsBrowser", dependencies: [
"SwiftPrettyPrint",
.product(name: "CasePaths", package: "swift-case-paths"),
.product(name: "SwiftUICommon", package: "SwiftUI-Common"),
]),
.testTarget(name: "UserDefaultsBrowserTests", dependencies: ["UserDefaultsBrowser"]),
]
Expand Down
95 changes: 95 additions & 0 deletions Sources/UserDefaultsBrowser/SwiftUICommon/Binding+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import CasePaths
import SwiftUI

public extension Binding {
//
// `Binding<Value>` -> `Binding<NewValue>`
//
func map<NewValue>(get: @escaping (Value) -> NewValue, set: @escaping (NewValue) -> Value) -> Binding<NewValue> {
.init(
get: { get(wrappedValue) },
set: { wrappedValue = set($0) }
)
}

//
// 🌱 Experimental.
//
// `Binding<Value>` -> `Binding<NewValue>` (`set` can be return `nil`)
//
// func map<NewValue>(get: @escaping (Value) -> NewValue, set: @escaping (NewValue) -> Value?) -> Binding<NewValue> {
// .init(
// get: { get(wrappedValue) },
// set: {
// if let value = set($0) {
// wrappedValue = value
// }
// }
// )
// }
//
//
// `Binding<T>` -> `Binding<T?>`
//
func optional() -> Binding<Value?> {
.init(
get: { self.wrappedValue },
set: {
if let value = $0 {
self.wrappedValue = value
}
}
)
}

//
// `Binding<T?>` -> `Binding<Bool>`
//
func isPresent<Wrapped>() -> Binding<Bool> where Value == Wrapped? {
.init(
get: { self.wrappedValue != nil },
set: {
if $0 == false {
self.wrappedValue = nil
}
}
)
}

//
// `Binding<T?>` -> `Binding<T>?`
//
func wrapped<Wrapped>() -> Binding<Wrapped>? where Value == Wrapped? {
if let value = wrappedValue {
return .init(
get: { value },
set: { wrappedValue = $0 }
)
} else {
return nil
}
}

//
// `Binding<Enum>` -> `Binding<AssociatedValue>?`
//
func `case`<AssociatedValue>(_ path: CasePath<Value, AssociatedValue>) -> Binding<AssociatedValue>? {
if let value = path.extract(from: wrappedValue) {
return .init(
get: { value },
set: { wrappedValue = path.embed($0) }
)
} else {
return nil
}
}
}

public extension Binding where Value == Bool {
func inverted() -> Binding<Bool> {
.init(
get: { !wrappedValue },
set: { wrappedValue = !$0 }
)
}
}
91 changes: 91 additions & 0 deletions Sources/UserDefaultsBrowser/SwiftUICommon/ResizableImage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import SwiftUI

public extension ResizableImage {
init(_ name: String, contentMode: ContentMode) {
self.init(image: Image(name), contentMode: contentMode)
}

init(systemName: String, contentMode: ContentMode) {
self.init(image: Image(systemName: systemName), contentMode: contentMode)
}
}

#if canImport(UIKit)
import UIKit

public extension ResizableImage {
init(uiImage: UIImage, contentMode: ContentMode) {
self.init(image: Image(uiImage: uiImage), contentMode: contentMode)
}
}
#endif

#if canImport(AppKit)
import AppKit

public extension ResizableImage {
init(nsImage: NSImage, contentMode: ContentMode) {
self.init(image: Image(nsImage: nsImage), contentMode: contentMode)
}
}
#endif

public struct ResizableImage: View {
private let image: Image
private let contentMode: ContentMode

@State private var imageSize: CGSize?

public var body: some View {
GeometryReader { geometry in
Group {
if let size = imageSize {
image
.resizable()
.aspectRatio(contentMode: contentMode)
.frame(size: size)
} else {
// 💡 1st time only
image
.onChangeSize {
imageSize = Self.calculateImageSize(
bounds: geometry.size,
originalSize: $0,
contentMode: contentMode
)
}
}
}
}
.frame(size: imageSize)
}

static func calculateImageSize(bounds: CGSize, originalSize size: CGSize, contentMode: ContentMode) -> CGSize {
if size.width < bounds.width {
return size
} else {
if (contentMode == .fit && size.height < size.width) ||
(contentMode == .fill && size.width < size.height)
{
//
// Calculated from `width`.
//
return .init(
width: bounds.width,
height: size.height * (bounds.width / size.width)
)
} else {
//
// Calculated from `height`.
//
// ⚠️ Can't get correct size from `bounds.height` when used in `ScrollView` and others.
//
let ratio = (size.width / size.height)
return .init(
width: bounds.width * ratio,
height: size.height * (bounds.width / size.width) * ratio
)
}
}
}
}
45 changes: 45 additions & 0 deletions Sources/UserDefaultsBrowser/SwiftUICommon/View+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import SwiftUI

extension View {
func enabled(_ enabled: Bool) -> some View {
disabled(enabled == false)
}

func extend<Content: View>(@ViewBuilder transform: (Self) -> Content) -> some View {
transform(self)
}

func frame(size: CGSize?) -> some View {
frame(width: size?.width, height: size?.height)
}

func onChangeSize(perform: @escaping (CGSize) -> Void) -> some View {
sizePreference()
.onChangeSizePreference(perform: perform)
}

func sizePreference() -> some View {
background(
GeometryReader { local in
Color.clear
.preference(key: SizeKey.self, value: local.size)
}
)
}

func onChangeSizePreference(perform: @escaping (CGSize) -> Void) -> some View {
onPreferenceChange(SizeKey.self) { size in
if let size = size {
perform(size)
}
}
}
}

private struct SizeKey: PreferenceKey {
static var defaultValue: CGSize?

static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
value = nextValue()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
//

import SwiftUI
import SwiftUICommon

public struct UserDefaultsBrowserContainer<Content: View>: View {
private let suiteNames: [String]
Expand Down
1 change: 0 additions & 1 deletion Sources/UserDefaultsBrowser/View/RowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import SwiftPrettyPrint
import SwiftUI
import SwiftUICommon

private enum Value {
case text(String)
Expand Down