Skip to content

Commit

Permalink
Merge pull request #12 from ryanlintott/beta
Browse files Browse the repository at this point in the history
Updated to Swift 6
  • Loading branch information
ryanlintott authored Sep 26, 2024
2 parents 9ecd806 + a0e2045 commit 36235c4
Show file tree
Hide file tree
Showing 62 changed files with 854 additions and 475 deletions.
15 changes: 2 additions & 13 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,6 @@ let package = Package(
name: "FrameUpTests",
dependencies: ["FrameUp"]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)

let swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("StrictConcurrency"),
.enableUpcomingFeature("DisableOutwardActorInference"),
.enableUpcomingFeature("IsolatedDefaultValues"),
]

for target in package.targets {
var settings = target.swiftSettings ?? []
settings.append(contentsOf: swiftSettings)
target.swiftSettings = settings
}
76 changes: 64 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ A collection of SwiftUI tools to help with layout.
- SwiftUI [`Layouts`](#layouts) like [`HFlowLayout`](#hflowlayout), [`VFlowLayout`](#vflowlayout), [`VMasonryLayout`](#vmasonrylayout), [`HMasonryLayout`](#hmasonrylayout), and [`LayoutThatFits`](#layoutthatfits)
- [`AutoRotatingView`](#autorotatingview) to set allowable orientations for a view.
- [Frame Adjustment](#frame-adjustment) tools like [`WidthReader`](#widthreader), [`HeightReader`](#heightreader), [`onSizeChange(perform:)`](#onsizechangeperform), [`keyboardHeight`](#keyboardHeight), [`.relativePadding`](#relativepaddingedges-lengthfactor), [`ScaledView`](#scaledview) and [`OverlappingImage`](#overlappingimage).
- [unclippedTextRenderer](#unclippedtextrenderer) for fixing clipped `Text`.
- [`SmartScrollView`](#smartscrollview) with optional scrolling, a content-fitable frame, and live edge inset values.
- [`TwoSidedView`](#twosidedview) and [`FlippingView`](#flippingview) for making flippable views with a different view on the back side.
- [`FlippingView`](#flippingview) and [`rotation3DEffect(back:)`](#rotation3deffectback) for making flippable views with a different view on the back side.
- [`TabMenu`](#tabmenu), a customizable iOS tab menu with `onReselect` and `onDoubleTap` functions.

Some widget-related tools
Expand Down Expand Up @@ -332,6 +333,20 @@ VStack(spacing: 0) {
}
```

## Text
### unclippedTextRenderer
*\*iOS 18+, macOS 15+, watchOS 11+, tvOS 18+, visionOS 2+*

SwiftUI `Text` has a clipping frame that cannot be adjusted and will occasionally clip the rendered text. This modifier applies an `UnclippedTextRenderer` that removes this clipping frame.

This modifier is unnecessary if another text renderer is used as all text renderers will remove the clipping frame.

```swift
Text("f")
.font(.custom("zapfino", size: 30))
.unclippedTextRenderer()
```

## SmartScrollView
*\*iOS only*

Expand All @@ -352,9 +367,26 @@ SmartScrollView(.vertical, showsIndicators: true, optionalScrolling: true, shrin
- If placed directly inside a NavigationView with a resizing header, this view may behave strangely when scrolling. To avoid this add 1 point of padding just inside the NavigationView.
- If the available space for this view grows for any reason other than screen rotation, this view might not grow to fill the space.

## TwoSidedView
### rotation3DEffect(angle:, axis:, anchor:, anchorZ, perspective:, back:)
An alternative to rotation3DEffect that provides a closure for views that will be seen on the back side of this view.
## FlippingView

### FlippingView
A two-sided view that can be flipped by tapping or swiping.

The axis, anchor, perspective, drag distance to flip, animation for tap to flip and more can all be customized.

For visionOS a slightly different initializer might be needed and the flips will occur in 3d space. If instead you want a perspective effect on a flat view you can use `PerspectiveFlippingView`

```swift
FlippingView(flips: $flips) {
Color.blue.overlay(Text("Up"))
} back: {
Color.red.overlay(Text("Back"))
}
```

### rotation3DEffect(angle:axis:anchor:anchorZ:perspective:backsideFlip:back:)
*\*deprecated in visionOS*
Renders a view’s content as if it’s rotated in three dimensions around the specified axis with a closure containing a different view to show on the back.

The example below is a view with two sides. One blue side that says "Front" and a red side on the back that says "Back". Changing the angle will show each side as it becomes visible.

Expand All @@ -365,17 +397,25 @@ Color.blue.overlay(Text("Front"))
}
```

### FlippingView
A two-sided view that can be flipped by tapping or swiping.
### rotation3DEffect(angle:axis:anchor:backsideFlip:back:)
*\*visionOS*
Rotates this view’s rendered output in three dimensions around the given axis of rotation with a closure containing a different view on the back. A minimum thickness that offsets the two views is required to ensure the side facing the user renders on top.
```swift
Color.blue.overlay(Text("Front"))
.rotation3DEffect(angle) {
Color.red.overlay(Text("Back"))
}
```

The axis, anchor, perspective, drag distance to flip, animation for tap to flip and more can all be customized.
### perspectiveRotationEffect(angle:axis:anchor:anchorZ:perspective:backsideFlip:back:)
*\*visionOS*
Renders a view’s content as if it’s rotated in three dimensions around the specified axis with a closure containing a different view to show on the back. The view is not actually rotated in 3d space.

```swift
FlippingView(flips: $flips) {
Color.blue.overlay(Text("Up"))
} back: {
Color.red.overlay(Text("Back"))
}
Color.blue.overlay(Text("Front"))
.rotation3DEffect(angle) {
Color.red.overlay(Text("Back"))
}
```

## TabMenu
Expand Down Expand Up @@ -485,6 +525,7 @@ If you like the SwiftUI `Layout` protocol but you need to target an older OS tha
An `FULayout` will work in the same way as a SwiftUI `Layout`. The main difference is it will require a `maxWidth` or `maxHeight` parameter when initializing in order to know the available space. This can be provided by `GeometryReader` or with [`WidthReader`](#widthreader) or [`HeightReader`](#heightreader) from this package.

### ViewBuilder
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
An `FULayout` uses `callAsFunction()` with a view builder so you can use it just like a SwiftUI `Layout`.

```swift
Expand All @@ -497,6 +538,7 @@ VFlow(maxWidth: 200) {
*Caution: This method uses Apple's private protocol `_VariadicView` under the hood. There is a small risk Apple could change the implementation so if this concerns you, use method 2 below.*

### `.forEach()`
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
This method works in a very similar way to `ForEach()`.

```swift
Expand All @@ -508,6 +550,7 @@ MyFULayout().forEach(["Hello", "World"], id: \.self) { item in

## FULayouts
### HFlow
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
The [`FULayout`](#fulayout) equivalent of [`HFlowLayout`](#hflowlayout).

A FrameUp `FULayout` that arranges views in horizontal rows flowing from one to the next with adjustable horizontal and vertical spacing and support for horiztonal and vertical alignment including a justified alignment that will space elements in completed rows evenly.
Expand All @@ -525,6 +568,7 @@ WidthReader { width in
```

### VFlow
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
The [`FULayout`](#fulayout) equivalent of [`VFlowLayout`](#vflowlayout).

A FrameUp `FULayout` that arranges views in vertical columns flowing from one to the next with adjustable horizontal and vertical spacing and support for horiztonal and vertical alignment including a justified alignment that will space elements in completed columns evenly.
Expand All @@ -542,6 +586,7 @@ WidthReader { width in
```

### HMasonry
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
The [`FULayout`](#fulayout) equivalent of [`HMasonryLayout`](#hmasonrylayout).

A FrameUp `FULayout` that arranges views into a set number of rows by adding each view to the shortest row.
Expand All @@ -558,6 +603,7 @@ HeightReader { height in
```

### VMasonry
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
The [`FULayout`](#fulayout) equivalent of [`VMasonryLayout`](#vmasonrylayout).

A FrameUp `FULayout` that arranges views into a set number of rows by adding each view to the shortest row.
Expand All @@ -574,6 +620,7 @@ WidthReader { width in
```

### FULayoutThatFits
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
The [`FULayout`](#fulayout) equivalent of [`LayoutThatFits`](#layoutthatfits).

An `FULayout` that picks the first provided layout that will fit the content in the provided maxWidth, maxHeight, or both. This is most helpful when switching between `HStackFULayout` and `VStackFULayout` as the content only needs to be provided once and will even animate when the stack changes.
Expand All @@ -593,6 +640,7 @@ FULayoutThatFits(
```

### FUViewThatFits
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
The [`FULayout`](#fulayout) equivalent of SwiftUI `ViewThatFits`.

An `FULayout` that presents the first view that fits the provided maxWidth, maxHeight, or both depending on which parameters are used.
Expand All @@ -618,15 +666,19 @@ WidthReader { width in
Alternative stack layouts that can be wrapped in [`AnyFULayout`](#anyfulayout) and then toggled between with animation. Useful when you want to toggle between VStack and HStack based on available space.

#### HStackFULayout
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
Similar to `HStack` but `Spacer()` cannot be used and content will always use a fixed size on the horizontal axis.

#### VStackFULayout
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
Similar to `VStack` but `Spacer()` cannot be used and content will always use a fixed size on the vertical axis.

#### ZStackFULayout
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
Similar to `ZStack` but content will always use a fixed size on both the vertical and horizontal axes.

### AnyFULayout
*\*Deprecated iOS 16, macOS 13, watchOS 7, tvOS 14, visionOS 1*
The [`FULayout`](#fulayout) equivalent of SwiftUI `AnyLayout`.

A type-erased FrameUp layout can be used to wrap multiple layouts and switch between them with animation.
Expand Down
17 changes: 8 additions & 9 deletions Sources/FrameUp/AutoRotatingView/AutoRotatingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import SwiftUI
/// A view that rotates and resizes the content frame to match device orientation.
public struct AutoRotatingView<Content: View>: View {
/// The current orientation of the content relative to the device.
@State private var contentOrientation: InterfaceOrientation? = nil
@State private var contentOrientation: FUInterfaceOrientation? = nil
/// The current orientation of the device.
@State private var interfaceOrientation: InterfaceOrientation? = nil
@State private var interfaceOrientation: FUInterfaceOrientation? = nil

/// Allowed orientations for the content.
let allowedOrientations: [InterfaceOrientation]
let allowedOrientations: [FUInterfaceOrientation]
/// Toggle to turn this modifier on or off.
let isOn: Bool
/// Animation to use for the orientation change.
Expand All @@ -31,15 +31,15 @@ public struct AutoRotatingView<Content: View>: View {
/// - allowedOrientations: Set of allowed orientations for this view. Default is all.
/// - isOn: Toggles ability to rotate views.
/// - animation: Animation to use when altering the view orientation.
/// - Returns: A view rotated to match a device orientations from an allowed orientation set.
public init(_ allowedOrientations: [InterfaceOrientation] = InterfaceOrientation.allCases, isOn: Bool = true, animation: Animation? = .default, @ViewBuilder content: () -> Content) {
/// - content: Content to be rotated to match a device orientations from an allowed orientation set.
public init(_ allowedOrientations: [FUInterfaceOrientation] = FUInterfaceOrientation.allCases, isOn: Bool = true, animation: Animation? = .default, @ViewBuilder content: () -> Content) {
self.allowedOrientations = allowedOrientations
self.isOn = isOn
self.animation = animation
self.content = content()
}

func newInterfaceOrientation(deviceOrientation: InterfaceOrientation?) -> InterfaceOrientation? {
func newInterfaceOrientation(deviceOrientation: FUInterfaceOrientation?) -> FUInterfaceOrientation? {
if let newSupportedOrientation = InfoDictionary.supportedInterfaceOrientations.first(where: { $0 == deviceOrientation }), newSupportedOrientation != interfaceOrientation {
return newSupportedOrientation
} else if interfaceOrientation == nil {
Expand All @@ -49,7 +49,7 @@ public struct AutoRotatingView<Content: View>: View {
}
}

func newContentOrientation(deviceOrientation: InterfaceOrientation?, interfaceOrientation: InterfaceOrientation?, allowedOrientations: [InterfaceOrientation]) -> InterfaceOrientation? {
func newContentOrientation(deviceOrientation: FUInterfaceOrientation?, interfaceOrientation: FUInterfaceOrientation?, allowedOrientations: [FUInterfaceOrientation]) -> FUInterfaceOrientation? {
if let newOrientation = deviceOrientation, allowedOrientations.contains(newOrientation), newOrientation != contentOrientation {
return newOrientation
} else if contentOrientation == nil {
Expand All @@ -59,8 +59,7 @@ public struct AutoRotatingView<Content: View>: View {
}
}

@MainActor
func changeOrientations(allowedOrientations: [InterfaceOrientation]? = nil) {
func changeOrientations(allowedOrientations: [FUInterfaceOrientation]? = nil) {
if isOn {
let allowedOrientations = allowedOrientations ?? self.allowedOrientations
/// if the new device orientation is a valid interface orientation it will not be nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@
// Created by Ryan Lintott on 2022-05-23.
//

#if os(iOS)
import SwiftUI

#if os(iOS)
@available(iOS, deprecated: 15.0, message: "This extension is no longer necessary as it's built into the API")
public enum InterfaceOrientation: CaseIterable, Sendable {
public enum FUInterfaceOrientation: CaseIterable, Sendable {
case portrait
case landscapeRight
case landscapeLeft
case portraitUpsideDown
}

//@available(iOS 15.0, *)
internal extension InterfaceOrientation {
nonisolated init?(key: String) {
internal extension FUInterfaceOrientation {
init?(key: String) {
switch key {
case "UIInterfaceOrientationPortrait":
self = .portrait
Expand Down Expand Up @@ -71,4 +69,17 @@ internal extension InterfaceOrientation {
}
}
}

@available(iOS 15, * )
internal extension FUInterfaceOrientation {
init?(_ interfaceOrientation: InterfaceOrientation) {
switch interfaceOrientation {
case .landscapeLeft: self = .landscapeLeft
case .landscapeRight: self = .landscapeRight
case .portrait: self = .portrait
case .portraitUpsideDown: self = .portraitUpsideDown
default: return nil
}
}
}
#endif
8 changes: 4 additions & 4 deletions Sources/FrameUp/AutoRotatingView/InfoDictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
// Created by Ryan Lintott on 2021-05-11.
//

#if os(iOS)
import SwiftUI

#if os(iOS)
struct InfoDictionary {
static let supportedInterfaceOrientations: [InterfaceOrientation] = {
enum InfoDictionary {
static let supportedInterfaceOrientations: [FUInterfaceOrientation] = {
if let orientations = Bundle.main.infoDictionary?["UISupportedInterfaceOrientations"] as? [String] {
return orientations.compactMap { InterfaceOrientation(key: $0) }
return orientations.compactMap { FUInterfaceOrientation(key: $0) }
} else {
return []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
// Created by Ryan Lintott on 2020-12-31.
//

#if os(iOS)
import SwiftUI

#if os(iOS)
extension View {
/// Rotates a view and alters it's frame to match device orientations from an allowed orientation set.
///
Expand All @@ -17,9 +17,9 @@ extension View {
/// - isOn: Toggle to turn this modifier on or off.
/// - animation: Animation to use when altering the view orientation.
/// - Returns: A view rotated to match a device orientations from an allowed orientation set.
@available(*, deprecated, message: "Use AutoRotatingView view instead of this modifier.")
public func rotationMatchingOrientation(_ allowedOrientations: [InterfaceOrientation]? = nil, isOn: Bool = true, withAnimation animation: Animation? = nil) -> some View {
AutoRotatingView(allowedOrientations ?? InterfaceOrientation.allCases, isOn: isOn, animation: animation) {
@available(*, deprecated, renamed: "AutoRotatingView", message: "Use AutoRotatingView view instead of this modifier.")
public func rotationMatchingOrientation(_ allowedOrientations: [FUInterfaceOrientation]? = nil, isOn: Bool = true, withAnimation animation: Animation? = nil) -> some View {
AutoRotatingView(allowedOrientations ?? FUInterfaceOrientation.allCases, isOn: isOn, animation: animation) {
self
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,17 @@
// Created by Ryan Lintott on 2021-05-14.
//

#if os(iOS)
import SwiftUI

#if os(iOS)
internal extension UIDeviceOrientation {
var interfaceOrientation: InterfaceOrientation? {
var interfaceOrientation: FUInterfaceOrientation? {
switch self {
case .portrait:
return .portrait
case .portraitUpsideDown:
return .portraitUpsideDown
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
default:
return nil
case .portrait: .portrait
case .portraitUpsideDown: .portraitUpsideDown
case .landscapeLeft: .landscapeLeft
case .landscapeRight: .landscapeRight
default: nil
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions Sources/FrameUp/FULayout/AnyFULayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import SwiftUI

/**
A type-erased instance of the FrameUp layout protocol.
A type-erased instance of ``FULayout``.
If you want to make a view that can toggle between layouts, wrap each one in AnyLayout.
If you want to make a view that can toggle between layouts, wrap each one in `AnyFULayout`.
*/
public struct AnyFULayout: FULayout {
/// The name of the wrapped layout (just used as a label)
Expand Down Expand Up @@ -52,6 +52,3 @@ public struct AnyFULayout: FULayout {
hasher.combine(String(describing: Self.self))
}
}

//@available(iOS 16, macOS 13, *)
//extension AnyFULayout: Layout { }
Loading

0 comments on commit 36235c4

Please sign in to comment.