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

Add SwiftUI proof of concept #481

Merged
merged 52 commits into from
Sep 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b4204d1
create Maps-SwiftUI example
zntfdr Jul 4, 2021
ec35ad7
cleanup initial project
zntfdr Jul 4, 2021
54d9009
add FloatingPanel framework
zntfdr Jul 4, 2021
22596bd
setup content view
zntfdr Jul 4, 2021
058e007
add root view
zntfdr Jul 4, 2021
d430cfa
use ignore safe area
zntfdr Jul 4, 2021
18df66c
add and use SearchBar
zntfdr Jul 4, 2021
1aff8e3
introduce and use ResultsList
zntfdr Jul 4, 2021
df23862
create and use ScrollViewPreferenceKey
zntfdr Jul 4, 2021
bdec4d8
remove print
zntfdr Jul 4, 2021
fa5c4c5
dismiss keyboard on drag
zntfdr Jul 4, 2021
642c941
manage onCancel action
zntfdr Jul 4, 2021
44a2669
set background color to nil
zntfdr Jul 4, 2021
3dd4341
add and use setAppearanceForPhone
zntfdr Jul 4, 2021
f3d3fa7
set contentMode = .fitToBounds
zntfdr Jul 4, 2021
a0db292
manage focus event
zntfdr Jul 4, 2021
39447aa
create and use FloatingPanelProxy
zntfdr Jul 4, 2021
f8f6809
create and use floatingPanel view modifier
zntfdr Jul 4, 2021
ff8dae6
group Representables
zntfdr Jul 4, 2021
1064f06
improve naming
zntfdr Jul 4, 2021
540146a
improve ResultsList definition
zntfdr Jul 4, 2021
a7ecd7d
improve FloatingPanelView API
zntfdr Jul 4, 2021
faabfd2
move proxy to Coordinator
zntfdr Jul 4, 2021
363f7d2
make api consistent
zntfdr Jul 4, 2021
7f99fbd
rename resultList -> resultsList
zntfdr Jul 4, 2021
8a28e9e
improve indentation
zntfdr Jul 4, 2021
d00f28c
add FloatingPanelProxy documentation
zntfdr Jul 5, 2021
9d929e1
add FloatingPanelView documentation
zntfdr Jul 5, 2021
478f559
rename statusBarView to statusBarBlur
zntfdr Jul 5, 2021
8a85018
remove NSObject requirement
zntfdr Jul 5, 2021
be6971e
add floatingPanel modifier documentation
zntfdr Jul 5, 2021
0eb88f0
improve documentation
zntfdr Jul 5, 2021
817bff8
fix typo
zntfdr Jul 5, 2021
9e0d786
remove default behaviour
zntfdr Jul 5, 2021
5c593bf
improve comment
zntfdr Jul 5, 2021
6148768
add documentation and credits
zntfdr Jul 5, 2021
5cfc2d7
create floatingPanelSurfaceAppearance View extension
zntfdr Jul 10, 2021
e92ff3f
use surfaceAppearance environment value
zntfdr Jul 10, 2021
4f401f1
create View+floatingPanelContentMode and View+floatingPanelContentIns…
zntfdr Jul 10, 2021
3a77b3f
use environment contentMode and contentInsetAdjustmentBehavior
zntfdr Jul 10, 2021
678bf6b
move SearchPanelPhoneDelegate to its own file
zntfdr Jul 10, 2021
c450d3b
add missing header
zntfdr Jul 10, 2021
dae0afc
pass delegate on floating panel modifier
zntfdr Jul 10, 2021
32f92db
minor adjustments
zntfdr Jul 10, 2021
4f1cd22
add dispatch
zntfdr Jul 10, 2021
3c565d6
use newer implementation
zntfdr Jul 25, 2021
ffa0f1a
improve documentation
zntfdr Jul 25, 2021
0943de5
re-order files
zntfdr Jul 25, 2021
4f5185b
Merge pull request #2 from zntfdr/maps-swiftui-2
zntfdr Jul 25, 2021
ba7ebd8
use ignore keyboard trick on floating panel, too
zntfdr Aug 9, 2021
d9092b2
add floatingPanelGrabberHandlePadding
zntfdr Aug 9, 2021
6663a06
fix import
zntfdr Aug 9, 2021
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
426 changes: 426 additions & 0 deletions Examples/Maps-SwiftUI/Maps-SwiftUI.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions Examples/Maps-SwiftUI/Maps/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import SwiftUI
import MapKit

struct ContentView: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.623198015869235, longitude: -122.43066818432008),
span: MKCoordinateSpan(latitudeDelta: 0.4425100023575723, longitudeDelta: 0.28543697435880233)
)

var body: some View {
ZStack {
Map(coordinateRegion: $region)
.ignoresSafeArea()
statusBarBlur
}
}

private var statusBarBlur: some View {
GeometryReader { geometry in
VisualEffectBlur()
.frame(height: geometry.safeAreaInsets.top)
.ignoresSafeArea()
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
121 changes: 121 additions & 0 deletions Examples/Maps-SwiftUI/Maps/FloatingPanel/FloatingPanelView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import FloatingPanel
import SwiftUI

/// A proxy for exposing the methods of the floating panel controller.
public struct FloatingPanelProxy {
/// The associated floating panel controller.
public weak var fpc: FloatingPanelController?

/// Tracks the specified scroll view to correspond with the scroll.
///
/// - Parameter scrollView: Specify a scroll view to continuously and
/// seamlessly work in concert with interactions of the surface view.
public func track(scrollView: UIScrollView) {
fpc?.track(scrollView: scrollView)
}

/// Moves the floating panel to the specified position.
///
/// - Parameters:
/// - floatingPanelState: The state to move to.
/// - animated: `true` to animate the transition to the new state; `false`
/// otherwise.
public func move(
to floatingPanelState: FloatingPanelState,
animated: Bool,
completion: (() -> Void)? = nil
) {
fpc?.move(to: floatingPanelState, animated: animated, completion: completion)
}
}

/// A view with an associated floating panel.
struct FloatingPanelView<Content: View, FloatingPanelContent: View>: UIViewControllerRepresentable {
/// A type that conforms to the `FloatingPanelControllerDelegate` protocol.
var delegate: FloatingPanelControllerDelegate?

/// The behavior for determining the adjusted content offsets.
@Environment(\.contentInsetAdjustmentBehavior) var contentInsetAdjustmentBehavior

/// Constants that define how a panel content fills in the surface.
@Environment(\.contentMode) var contentMode

/// The floating panel grabber handle offset.
@Environment(\.grabberHandlePadding) var grabberHandlePadding

/// The floating panel `surfaceView` appearance.
@Environment(\.surfaceAppearance) var surfaceAppearance

/// The view builder that creates the floating panel parent view content.
@ViewBuilder var content: Content

/// The view builder that creates the floating panel content.
@ViewBuilder var floatingPanelContent: (FloatingPanelProxy) -> FloatingPanelContent

public func makeUIViewController(context: Context) -> UIHostingController<Content> {
let hostingController = UIHostingController(rootView: content)
hostingController.view.backgroundColor = nil
// We need to wait for the current runloop cycle to complete before our
// view is actually added (into the view hierarchy), otherwise the
// environment is not ready yet.
DispatchQueue.main.async {
context.coordinator.setupFloatingPanel(hostingController)
}
return hostingController
}

public func updateUIViewController(
_ uiViewController: UIHostingController<Content>,
context: Context
) {
context.coordinator.updateIfNeeded()
}

public func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}

/// `FloatingPanelView` coordinator.
///
/// Responsible to setup the view hierarchy and floating panel.
final class Coordinator {
private let parent: FloatingPanelView<Content, FloatingPanelContent>
private lazy var fpc = FloatingPanelController()

init(parent: FloatingPanelView<Content, FloatingPanelContent>) {
self.parent = parent
}

func setupFloatingPanel(_ parentViewController: UIViewController) {
updateIfNeeded()
let panelContent = parent.floatingPanelContent(FloatingPanelProxy(fpc: fpc))
let hostingViewController = UIHostingController(
rootView: panelContent,
ignoresKeyboard: true
)
hostingViewController.view.backgroundColor = nil
fpc.set(contentViewController: hostingViewController)
fpc.addPanel(toParent: parentViewController, at: 1, animated: false)
}

func updateIfNeeded() {
if fpc.contentInsetAdjustmentBehavior != parent.contentInsetAdjustmentBehavior {
fpc.contentInsetAdjustmentBehavior = parent.contentInsetAdjustmentBehavior
}
if fpc.contentMode != parent.contentMode {
fpc.contentMode = parent.contentMode
}
if fpc.delegate !== parent.delegate {
fpc.delegate = parent.delegate
}
if fpc.surfaceView.grabberHandlePadding != parent.grabberHandlePadding {
fpc.surfaceView.grabberHandlePadding = parent.grabberHandlePadding
}
if fpc.surfaceView.appearance != parent.surfaceAppearance {
fpc.surfaceView.appearance = parent.surfaceAppearance
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import SwiftUI

/// This extension makes sure SwiftUI views are not affected by iOS keyboard.
///
/// Credits to https://steipete.me/posts/disabling-keyboard-avoidance-in-swiftui-uihostingcontroller/
extension UIHostingController {
public convenience init(rootView: Content, ignoresKeyboard: Bool) {
self.init(rootView: rootView)

if ignoresKeyboard {
guard let viewClass = object_getClass(view) else { return }

let viewSubclassName = String(
cString: class_getName(viewClass)
).appending("_IgnoresKeyboard")

if let viewSubclass = NSClassFromString(viewSubclassName) {
object_setClass(view, viewSubclass)
} else {
guard
let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String,
let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0)
else { return }

if let method = class_getInstanceMethod(
viewClass,
NSSelectorFromString("keyboardWillShowWithNotification:")
) {
let keyboardWillShow: @convention(block) (AnyObject, AnyObject) -> Void = { _, _ in }
class_addMethod(
viewSubclass,
NSSelectorFromString("keyboardWillShowWithNotification:"),
imp_implementationWithBlock(keyboardWillShow),
method_getTypeEncoding(method)
)
}
objc_registerClassPair(viewSubclass)
object_setClass(view, viewSubclass)
}
}
}
}
31 changes: 31 additions & 0 deletions Examples/Maps-SwiftUI/Maps/FloatingPanel/View+floatingPanel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import FloatingPanel
import SwiftUI

extension View {
/// Presents a floating panel using the given closure as its content.
///
/// The modifier's content view builder receives a `FloatingPanelProxy`
/// instance; you use the proxy's methods to interact with the associated
/// `FloatingPanelController`.
///
/// - Parameters:
/// - delegate: A type that conforms to the
/// `FloatingPanelControllerDelegate` protocol. You have comprehensive
/// control over the floating panel behavior when you use a delegate.
/// - floatingPanelContent: The floating panel content. This view builder
/// receives a `FloatingPanelProxy` instance that you use to interact
/// with the `FloatingPanelController`.
public func floatingPanel<FloatingPanelContent: View>(
delegate: FloatingPanelControllerDelegate? = nil,
@ViewBuilder _ floatingPanelContent: @escaping (_: FloatingPanelProxy) -> FloatingPanelContent
) -> some View {
FloatingPanelView(
delegate: delegate,
content: { self },
floatingPanelContent: floatingPanelContent
)
.ignoresSafeArea()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import FloatingPanel
import SwiftUI

struct ContentInsetKey: EnvironmentKey {
static var defaultValue: FloatingPanelController.ContentInsetAdjustmentBehavior = .always
}

extension EnvironmentValues {
/// The behavior for determining the adjusted content offsets.
var contentInsetAdjustmentBehavior: FloatingPanelController.ContentInsetAdjustmentBehavior {
get { self[ContentInsetKey.self] }
set { self[ContentInsetKey.self] = newValue }
}
}

extension View {
/// Sets the content inset adjustment behavior for floating panels within
/// this view.
///
/// Use this modifier to set a specific content inset adjustment behavior
/// for floating panel instances within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelContentInsetAdjustmentBehavior(.never)
///
/// - Parameter contentInsetAdjustmentBehavior: The content inset adjustment
/// behavior to set.
public func floatingPanelContentInsetAdjustmentBehavior(
_ contentInsetAdjustmentBehavior: FloatingPanelController.ContentInsetAdjustmentBehavior
) -> some View {
environment(\.contentInsetAdjustmentBehavior, contentInsetAdjustmentBehavior)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import FloatingPanel
import SwiftUI

struct ContentModeKey: EnvironmentKey {
static var defaultValue: FloatingPanelController.ContentMode = .static
}

extension EnvironmentValues {
/// Used to determine how the floating panel controller lays out the content
/// view when the surface position changes.
var contentMode: FloatingPanelController.ContentMode {
get { self[ContentModeKey.self] }
set { self[ContentModeKey.self] = newValue }
}
}

extension View {
/// Sets the content mode for floating panels within this view.
///
/// Use this modifier to set a specific content mode for floating panel
/// instances within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelContentMode(.static)
///
/// - Parameter contentMode: The content mode to set.
public func floatingPanelContentMode(
_ contentMode: FloatingPanelController.ContentMode
) -> some View {
environment(\.contentMode, contentMode)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import FloatingPanel
import SwiftUI

struct GrabberHandlePaddingKey: EnvironmentKey {
static var defaultValue: CGFloat = 6.0
}

extension EnvironmentValues {
/// The offset of the grabber handle from the interactive edge.
var grabberHandlePadding: CGFloat {
get { self[GrabberHandlePaddingKey.self] }
set { self[GrabberHandlePaddingKey.self] = newValue }
}
}

extension View {
/// Sets the grabber handle padding for floating panels within this view.
///
/// Use this modifier to set a specific padding to floating panel instances
/// within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelGrabberHandlePadding(16)
///
/// - Parameter padding: The grabber handle padding to set.
public func floatingPanelGrabberHandlePadding(
_ padding: CGFloat
) -> some View {
environment(\.grabberHandlePadding, padding)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.

import FloatingPanel
import SwiftUI

struct SurfaceAppearanceKey: EnvironmentKey {
static var defaultValue = SurfaceAppearance()
}

extension EnvironmentValues {
/// The appearance of a surface view.
var surfaceAppearance: SurfaceAppearance {
get { self[SurfaceAppearanceKey.self] }
set { self[SurfaceAppearanceKey.self] = newValue }
}
}

extension View {
/// Sets the surface appearance for floating panels within this view.
///
/// Use this modifier to set a specific surface appearance for floating
/// panel instances within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelSurfaceAppearance(.transparent)
///
/// extension SurfaceAppearance {
/// static var transparent: SurfaceAppearance {
/// let appearance = SurfaceAppearance()
/// appearance.backgroundColor = .clear
/// return appearance
/// }
/// }
///
/// - Parameter surfaceAppearance: The surface appearance to set.
public func floatingPanelSurfaceAppearance(
_ surfaceAppearance: SurfaceAppearance
) -> some View {
environment(\.surfaceAppearance, surfaceAppearance)
}
}
Loading