Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FS-29: Support iPad Presentation Size #30

Merged
merged 7 commits into from
Apr 2, 2024
Merged
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: 10 additions & 0 deletions FlowStackExample/FlowStackExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
ED183AB22BB4D99A0041F7C7 /* FlowStackExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FlowStackExample.entitlements; sourceTree = "<group>"; };
ED8DE15D2A6765F500215165 /* FlowStackExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlowStackExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
ED8DE1642A6765F600215165 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
ED8DE1672A6765F600215165 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -64,6 +65,7 @@
ED8DE15F2A6765F500215165 /* FlowStackExample */ = {
isa = PBXGroup;
children = (
ED183AB22BB4D99A0041F7C7 /* FlowStackExample.entitlements */,
EDA1DD372A67693A00D82A9D /* FlowStackExampleApp.swift */,
EDA1DD342A67693A00D82A9D /* ContentView.swift */,
EDFD07BB2A6F319E00CB0737 /* ProductRow.swift */,
Expand Down Expand Up @@ -303,6 +305,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = FlowStackExample/FlowStackExample.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"FlowStackExample/Preview Content\"";
Expand All @@ -313,6 +316,7 @@
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -321,6 +325,8 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.velosmobile.FlowStackExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand All @@ -332,6 +338,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = FlowStackExample/FlowStackExample.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"FlowStackExample/Preview Content\"";
Expand All @@ -342,6 +349,7 @@
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -350,6 +358,8 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.velosmobile.FlowStackExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down
27 changes: 21 additions & 6 deletions FlowStackExample/FlowStackExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,40 @@ import SwiftUI
import FlowStack

struct ContentView: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass

let cornerRadius: CGFloat = 24

var body: some View {
FlowStack {
ScrollView {
LazyVStack(alignment: .center, spacing: 24, pinnedViews: [], content: {
ForEach(Product.allProducts) { product in
FlowLink(value: product, configuration: .init(cornerRadius: cornerRadius)) {
ProductRow(product: product, cornerRadius: cornerRadius)
}
Group {
if horizontalSizeClass == .compact {
LazyVStack(alignment: .center, spacing: 24, pinnedViews: [], content: {
content
})

} else {
LazyVGrid(columns: [GridItem(.flexible(), spacing: 16), GridItem(.flexible())], alignment: .center, spacing: 16, content: {
content
})
}
})
}
.padding(.horizontal)
}
.flowDestination(for: Product.self) { product in
ProductDetails(product: product)
}
}
}

var content: some View {
ForEach(Product.allProducts) { product in
FlowLink(value: product, configuration: .init(cornerRadius: cornerRadius)) {
ProductRow(product: product, cornerRadius: cornerRadius)
}
}
}
}

struct ContentView_Previews: PreviewProvider {
Expand Down
10 changes: 10 additions & 0 deletions FlowStackExample/FlowStackExample/FlowStackExample.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
7 changes: 5 additions & 2 deletions FlowStackExample/FlowStackExample/ProductRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ struct ProductRow: View {
var cornerRadius: CGFloat

var body: some View {
image(url: product.imageUrl)
.allowsHitTesting(false) // https://stackoverflow.com/a/74711565
Color.clear
.aspectRatio(4 / 3, contentMode: .fill)
.overlay {
image(url: product.imageUrl)
.allowsHitTesting(false) // https://stackoverflow.com/a/74711565
}
.overlay(alignment: .topTrailing) {
Text(product.name)
.font(.system(size: 48))
Expand Down
20 changes: 13 additions & 7 deletions Sources/FlowStack/FlowStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,25 @@ public struct FlowStack<Root: View, Overlay: View>: View {
.ignoresSafeArea()
.zIndex(Double(element.index + 1) - 0.1)
.id(element.hashValue)
.onTapGesture {
flowDismissAction()
}
}
}

private var pathToUse: Binding<FlowPath> {
usesInternalPath ? $internalPath : _path
}

private var flowDismissAction: FlowDismissAction {
FlowDismissAction(
onDismiss: {
withTransaction(transaction) {
pathToUse.wrappedValue.removeLast()
}
})
}

private var transaction: Transaction {
var transaction = Transaction(animation: animation)
transaction.disablesAnimations = true
Expand Down Expand Up @@ -265,13 +277,7 @@ public struct FlowStack<Root: View, Overlay: View>: View {
.environment(\.flowPath, pathToUse)
.environment(\.flowTransaction, transaction)
.environmentObject(destinationLookup)
.environment(\.flowDismiss, FlowDismissAction(
onDismiss: {
withTransaction(transaction) {
pathToUse.wrappedValue.removeLast()
}
})
)
.environment(\.flowDismiss, flowDismissAction)
}
}

Expand Down
49 changes: 44 additions & 5 deletions Sources/FlowStack/FlowTransition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,30 @@ extension AnyTransition {
@State private var isDisabled: Bool = false
@State var isDismissing: Bool = false
@State private var snapCornerRadiusZero: Bool = true
@State private var availableSize: CGSize = .zero

private var snapshotPercent: CGFloat {
max(0, 1 - percent / 0.2)
}

@Environment(\.flowDismiss) var dismiss
@Environment(\.flowTransaction) var transaction
@Environment(\.horizontalSizeClass) var horizontalSizeClass

var cornerRadius: CGFloat { context.cornerRadius + ((UIScreen.displayCornerRadius ?? 20) - context.cornerRadius) * percent }

var isPresentedFullscreen: Bool {
horizontalSizeClass == .compact || availableSize.width - 2 * Constants.minVerticalPadding < Constants.maxWidth
}

var conditionalCornerRadius: CGFloat {
if percent >= 1 {
if snapCornerRadiusZero {
return 0
if isPresentedFullscreen {
if percent >= 1 {
if snapCornerRadiusZero {
return 0
} else {
return cornerRadius
}
} else {
return cornerRadius
}
Expand Down Expand Up @@ -143,13 +153,30 @@ extension AnyTransition {
let zoomRect = CGRect(
x: (proxy.size.width / 2) * percent + rect.midX * (1 - percent) + (pullOffset ?? .zero).x / 3,
y: (proxy.size.height / 2) * percent + rect.midY * (1 - percent) + (pullOffset ?? .zero).y / 3,
width: rect.width + ((proxy.size.width - rect.width) * max(0, percent) * (1 - pullPercent)),
height: rect.height + ((proxy.size.height - rect.height) * max(0, percent) * (1 - pullPercent))
width: rect.width + ((presentationSize(availableSize: proxy.size).width - rect.width) * max(0, percent) * (1 - pullPercent)),
height: rect.height + ((presentationSize(availableSize: proxy.size).height - rect.height) * max(0, percent) * (1 - pullPercent))
)

return zoomRect
}

struct Constants {
static let maxWidth: CGFloat = 706
static let maxHeight: CGFloat = 998
static let minVerticalPadding: CGFloat = 44
}

private func presentationSize(availableSize: CGSize) -> CGSize {

if horizontalSizeClass == .regular && availableSize.width - 2 * Constants.minVerticalPadding >= Constants.maxWidth {
let width = Constants.maxWidth
let height = min(Constants.maxHeight, availableSize.height - Constants.minVerticalPadding * 2)
return CGSize(width: width, height: height)
} else {
return availableSize
}
}

func body(content: Content) -> some View {
GeometryReader { proxy in
let zoomRect = zoomRect(with: proxy, anchor: context.overrideAnchor ?? context.anchor, percent: percent, pullOffset: panOffset)
Expand All @@ -173,6 +200,10 @@ extension AnyTransition {
.onPreferenceChange(InteractiveDismissDisabledKey.self) { isDisabled in
self.isDisabled = isDisabled
}
.preference(key: SizePreferenceKey.self, value: proxy.size)
.onPreferenceChange(SizePreferenceKey.self, perform: { value in
availableSize = value
})
.overlay {
if let image = context.snapshot, percent < 1 {
Image(uiImage: image)
Expand All @@ -198,3 +229,11 @@ extension AnyTransition {
}
}
}

struct SizePreferenceKey: PreferenceKey {
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}

static var defaultValue: CGSize = .zero
}
2 changes: 1 addition & 1 deletion Sources/FlowStack/UIScreen+DisplayCorners.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ extension UIScreen {
return nil
}

return cornerRadius
return cornerRadius > 0 ? cornerRadius : 12
}()
}
Loading