Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 837edc6abf | |||
| 20fda3888f | |||
| 4728c6848b | |||
| 70c945c9f5 |
@@ -1 +0,0 @@
|
||||
github: SCENEE
|
||||
@@ -2,7 +2,7 @@
|
||||
>
|
||||
> Please remove this line and everything above it before submitting.
|
||||
|
||||
### Description
|
||||
### Short description
|
||||
|
||||
### Expected behavior
|
||||
|
||||
@@ -12,29 +12,16 @@
|
||||
|
||||
**Code example that reproduces the issue**
|
||||
|
||||
|
||||
**How do you display panel(s)?**
|
||||
|
||||
* Add as child view controllers
|
||||
* Present modally
|
||||
|
||||
**How many panels do you displays?**
|
||||
|
||||
* 1
|
||||
* 2+
|
||||
|
||||
### Environment
|
||||
|
||||
**Library version**
|
||||
|
||||
**Installation method**
|
||||
|
||||
* CocoaPods
|
||||
* Carthage
|
||||
* Swift Package Manager
|
||||
|
||||
- [ ] CocoaPods
|
||||
- [ ] Carthage
|
||||
- [ ] Git submodules
|
||||
|
||||
**iOS version(s)**
|
||||
|
||||
|
||||
**Xcode version**
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- swift: "5.6"
|
||||
xcode: "13.3.1"
|
||||
runsOn: macos-12
|
||||
- swift: "5.5"
|
||||
xcode: "13.2.1"
|
||||
runsOn: macos-11
|
||||
- swift: "5.4"
|
||||
xcode: "12.5.1"
|
||||
runsOn: macos-11
|
||||
- swift: "5.3"
|
||||
xcode: "12.4"
|
||||
runsOn: macos-10.15
|
||||
- swift: "5.2"
|
||||
xcode: "11.7"
|
||||
runsOn: macos-10.15
|
||||
- swift: "5.1"
|
||||
xcode: "11.3.1"
|
||||
runsOn: macos-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Building in Swift ${{ matrix.swift }}
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: "15.4"
|
||||
xcode: "13.3.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
runsOn: macos-12
|
||||
- os: "15.2"
|
||||
xcode: "13.2.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "14.5"
|
||||
xcode: "12.5.1"
|
||||
sim: "iPhone 12 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "14.4"
|
||||
xcode: "12.4"
|
||||
sim: "iPhone 12 Pro"
|
||||
runsOn: macos-10.15
|
||||
- os: "13.7"
|
||||
xcode: "11.7"
|
||||
sim: "iPhone 11 Pro"
|
||||
runsOn: macos-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Testing in iOS ${{ matrix.os }}
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}'
|
||||
|
||||
example:
|
||||
runs-on: macos-12
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.3.1.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- example: "Maps"
|
||||
- example: "Stocks"
|
||||
- example: "Samples"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Building ${{ matrix.example }}
|
||||
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
|
||||
|
||||
swiftpm:
|
||||
runs-on: macos-12
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.3.1.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: "arm64-apple-ios15.4-simulator"
|
||||
- target: "x86_64-apple-ios15.4-simulator"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: "Swift Package Manager build"
|
||||
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
|
||||
|
||||
carthage:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: "Carthage build"
|
||||
run: carthage build --use-xcframeworks --no-skip-current
|
||||
|
||||
cocoapods:
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: "CocoaPods: pod lib lint"
|
||||
run: pod lib lint --allow-warnings
|
||||
- name: "CocoaPods: pod spec lint"
|
||||
run: pod spec lint --allow-warnings
|
||||
@@ -17,7 +17,6 @@ DerivedData/
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
IDEWorkspaceChecks.plis
|
||||
|
||||
## Other
|
||||
*.moved-aside
|
||||
@@ -31,9 +30,6 @@ IDEWorkspaceChecks.plis
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Swift Package Manager Specific
|
||||
.swiftpm/
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
language: objective-c
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
cache:
|
||||
directories:
|
||||
- /usr/local/Homebrew
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
before_cache:
|
||||
- brew cleanup
|
||||
env:
|
||||
global:
|
||||
- LANG=en_US.UTF-8
|
||||
- LC_ALL=en_US.UTF-8
|
||||
jobs:
|
||||
include:
|
||||
- stage: "Builds"
|
||||
osx_image: xcode9.4
|
||||
script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.1 clean build
|
||||
name: "Swift 4.1"
|
||||
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.2 clean build
|
||||
osx_image: xcode10
|
||||
name: "Swift 4.2"
|
||||
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.0 clean build
|
||||
osx_image: xcode10.2
|
||||
name: "Swift 5.0"
|
||||
|
||||
- stage: "Tests"
|
||||
osx_image: xcode10.2
|
||||
script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=10.3.1,name=iPhone SE'
|
||||
name: "iPhone SE (iOS 10.3)"
|
||||
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=11.4,name=iPhone 7'
|
||||
osx_image: xcode10.2
|
||||
name: "iPhone 7 (iOS 11.4)"
|
||||
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=12.2,name=iPhone X'
|
||||
osx_image: xcode10.2
|
||||
name: "iPhone X (iOS 12.2)"
|
||||
|
||||
- stage: Carthage
|
||||
osx_image: xcode10.2
|
||||
before_install:
|
||||
- brew update
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
script:
|
||||
- carthage build --no-skip-current
|
||||
|
||||
- stage: CocoaPods
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- pod spec lint
|
||||
- pod lib lint
|
||||
|
||||
- stage: Build examples
|
||||
osx_image: xcode10.2
|
||||
script: xcodebuild -scheme Maps -sdk iphonesimulator clean build
|
||||
name: "Maps"
|
||||
- script: xcodebuild -scheme Stocks -sdk iphonesimulator clean build
|
||||
osx_image: xcode10.2
|
||||
name: "Stocks"
|
||||
- script: xcodebuild -scheme Samples -sdk iphonesimulator clean build
|
||||
osx_image: xcode10.2
|
||||
name: "Samples"
|
||||
@@ -1,294 +0,0 @@
|
||||
# FloatingPanel 2.0 Migration Guide
|
||||
|
||||
FloatingPanel 2.0 is the latest major release of FloatingPanel. As a major release, following Semantic Versioning conventions, 2.0 introduces API-breaking changes.
|
||||
|
||||
This guide is provided in order to ease the transition of existing applications using FloatingPanel 1.x to the latest APIs, as well as explain the design and structure of new and updated functionality.
|
||||
|
||||
## Updated Minimum Requirements
|
||||
|
||||
* Swift 5.0
|
||||
* iOS 11 (iOS 10 is still the deployment target, but not tested well)
|
||||
* Xcode 11.0
|
||||
|
||||
## Benefits of Upgrading
|
||||
|
||||
* __Top, left and right positioned panel__
|
||||
* FloatingPanel is not just a library for a bottom positioned panel, but also top, left and right positioned ones.
|
||||
* __Objective-C compatibility__
|
||||
* The entire APIs are exposed in Objective-C. So you can use them in Objective-C directly.
|
||||
* __Flexible and explicit layout customization__
|
||||
* `FloatingPanelLayout` is redesigned. There is no implicit rules to lay out a panel anymore.
|
||||
* __New spring animation without UIViewPropertyAnimator__
|
||||
* The new spring animation uses [Numeric springing](http://allenchou.net/2015/04/game-math-precise-control-over-numeric-springing/) which is a very powerful tool for procedural animation. Therefore a library consumer is easy to modify a panel behavior by 2 paramters of the deceleration rate and response time.
|
||||
* __Handle the panel position anytime__
|
||||
* `floatingPanelDidMove(_:)` delegate method is also called while a panel is moving. The method behavior becomes same as `scrollViewDidScroll(_:)` in `UIScrollViewDelegate`. And in the method a library consumer is able to change a panel location.
|
||||
* __Update the removal interaction's invocation__
|
||||
* Now you can invoke the removal interaction at any time where you want. There is no restrictions in the library.
|
||||
* __Fix many issues depending on API design__
|
||||
* See the following sections for details.
|
||||
|
||||
## API Name Changes
|
||||
|
||||
* `FloatingPanelPosition` is now `FloatingPanelState`.
|
||||
* `FloatingPanelPosition` in v2 is used to specify a panel position(top, left, bottom and right) in a screen.
|
||||
* `FloatingPanelSurfaceView` is `SurfaceView` only in Swift.
|
||||
* `FloatingPanelBackdropView` is `BackdropView` only in Swift.
|
||||
* `FloatingPanelGrabberHandleView` is `GrabberView` only in Swift.
|
||||
* "decelerate" term is replaced with "attract" because the panel's behavior is not unidirectional, but going back and forth so that it is settled to a location.
|
||||
|
||||
## `FloatingPanelController`
|
||||
|
||||
* `layout` and `behavior` properties can be changed directly without using the delegate methods.
|
||||
|
||||
```swift
|
||||
fpc.behavior = SearchPaneliPadBehavior()
|
||||
fpc.layout = SearchPaneliPadLayout()
|
||||
fpc.invalidateLayout() // If needed
|
||||
```
|
||||
|
||||
* The second argument of `addPanel(toParent:)` changes to specify an index of subviews of a view in which a panel is added.
|
||||
|
||||
```diff
|
||||
- public func addPanel(toParent parent: UIViewController, belowView: UIView? = nil, animated: Bool = false) {
|
||||
+ public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false) {
|
||||
```
|
||||
|
||||
* `surfaceOriginY` is now `surfaceLocation`.
|
||||
* `updateLayout` is now `invalidateLayout`.
|
||||
* The scroll tracking API is changed a bit to support multiple scroll view tracking in the future.
|
||||
* Now `untrack(scrollView:)` is used to disable the scroll tracking.
|
||||
|
||||
## `FloatingPanelControllerDelegate`
|
||||
|
||||
* `floatingPanelDidEndDragging(_ vc:willAttract:)` is added to check whether a panel will continue to move after dragging.
|
||||
* `floatingPanelDidMove(_:)` behavior changes. The method is also called in the spring animation.
|
||||
* The removal interaction delegate is updated.
|
||||
* `floatingPanel(_:shouldRemoveAt:with:)` is added to determine whether it invokes the removal interaction in any state.
|
||||
* `floatingPanelWillRemove(_:)` is added.
|
||||
* `floatingPanel(_: FloatingPanelController, layoutFor size: CGSize)` is added to respond to a layout change in regular size classes on iPad.
|
||||
|
||||
```swift
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout {
|
||||
if aCondition(for: size) {
|
||||
return SearchPanelLayout()
|
||||
}
|
||||
return SearchPanel2Layout()
|
||||
}
|
||||
```
|
||||
|
||||
* The `targetState` argument type of `floatingPanelWillEndDragging(_:withVelocity:targetState:)` is changed from `FloatingPanelState` to `UnsafeMutablePointer<FloatingPanelState>` to modify a target state on demand.
|
||||
|
||||
```swift
|
||||
func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
|
||||
switch targetState.pointee {
|
||||
case .full:
|
||||
// do something...
|
||||
case .half:
|
||||
if aCondition {
|
||||
targetState.pointee = .tip
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* `floatingPanel(_:behaviorFor:)`
|
||||
* Please update `FloatingPanelController.behavior` directly.
|
||||
* `floatingPanel(_:shouldRecognizeSimultaneouslyWith:)`
|
||||
* Please use `FloatingPanelController.panGestureRecognizer.delegateProxy`.
|
||||
|
||||
## `FloatingPanelLayout`
|
||||
|
||||
* `position` property is added to determine a panel position.
|
||||
* `initialPosition` is now `initialState`.
|
||||
* `supportedPositions` and `insetFor(position:)` are replaced with `anchors` property.
|
||||
* `backdropAlphaFor(position:)` is now `backdropAlpha(for:)`.
|
||||
|
||||
```swift
|
||||
class SearchPanelPadLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .top
|
||||
let initialState: FloatingPanelState = .tip
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
...
|
||||
]
|
||||
}
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.3
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### New `FloatingPanelLayoutAnchoring` classes
|
||||
|
||||
The following objects adopting `FloatingPanelLayoutAnchoring` protocol are added to configure the flexible and explicit layout.
|
||||
|
||||
#### `FloatingPanelLayoutAnchor`
|
||||
|
||||
This class is used to specify a panel layout using insets from a rectangle area of the superview or safe area.
|
||||
|
||||
* `FloatingPanelFullScreenLayout` is replaced with anchors using `.superview` reference guide.
|
||||
* `FloatingPanelLayoutAnchor(fractionalInset:edge:referenceGuide:)` lets you lay out a panel at a relative position in a reference rectangle area.
|
||||
|
||||
```swift
|
||||
// Before:
|
||||
class MyPanelLayout: FloatingPanelLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return 16.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 44.0
|
||||
case .hidden: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After:
|
||||
class MyPanelLayout: FloatingPanelLayout {
|
||||
var position: FloatingPanelPosition = .bottom
|
||||
var initialState: FloatingPanelState { .half }
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `FloatingPanelIntrinsicLayoutAnchor`
|
||||
|
||||
This class is used to specify a panel layout using offsets from the intrinsic size layout.
|
||||
|
||||
* This replaces `FloatingPanelIntrinsicLayout`.
|
||||
* This is also able to configure a fractional layout in the intrinsic size.
|
||||
|
||||
```swift
|
||||
// Before:
|
||||
class MyPanelIntrinsicLayout: FloatingPanelIntrinsicLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return 16.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 44.0
|
||||
case .hidden: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After:
|
||||
class MyPanelIntrinsicLayout: FloatingPanelLayout {
|
||||
var position: FloatingPanelPosition = .bottom
|
||||
var initialState: FloatingPanelState { .full }
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 16.0, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* `.topInteractionBuffer` and `.bottomInteractionBuffer`.
|
||||
* Please control the max/min range of the motion in `floatingPanelDidMove(_:)` delegate method as below.
|
||||
|
||||
```swift
|
||||
func floatingPanelDidMove(_ fpc: FloatingPanelController) {
|
||||
if fpc.isAttracting == false {
|
||||
let loc = fpc.surfaceLocation
|
||||
let minY = fpc.surfaceLocation(for: .full).y - 6.0
|
||||
let maxY = fpc.surfaceLocation(for: .tip).y + 6.0
|
||||
fpc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `FloatingPanelBehavior`
|
||||
|
||||
* `.springDecelerationRate` and `.springResponseTime` properties are added to control the new spring effect of Numeric springing.
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* `addAnimator(_:to:)`, `removeAnimator(_:from:)`
|
||||
* They are moved into `floatingPanel(_:animatorForPresentingTo:)` and `floatingPanel(_:animatorForDismissingWith:)` of `FloatingPanelControllerDelegate` because they are used for view transitions.
|
||||
* `interactionAnimator(_:to:with:)`, `moveAnimator(_:from:to:)`
|
||||
* They are removed because the animators are replaced with the new spring effect.
|
||||
* `removalVelocity`, `removalProgress`
|
||||
* They are replaced with `floatingPanel(_:shouldRemoveAt:with:)` of `FloatingPanelControllerDelegate`
|
||||
* `removalInteractionAnimator(_:with:)`
|
||||
* It is integrated with `floatingPanel(_:animatorForDismissingWith:)` of `FloatingPanelControllerDelegate`.
|
||||
|
||||
## `SurfaceView`
|
||||
|
||||
* `SurfaceAppearance` class and `SurfaceView.appearance` property are added to specify the rounding corners, shadows and background color.
|
||||
* `SurfaceView.appearance` property avoids `Ambiguous use of 'cornerRadius'` error, for instance.
|
||||
* `SurfaceAppearance` enables to apply layered box shadows into a surface to materialize it.
|
||||
|
||||
```swift
|
||||
// Before:
|
||||
fpc.surfaceView.cornerRadius = 6.0
|
||||
fpc.surfaceView.backgroundColor = .clear
|
||||
fpc.surfaceView.shadowHidden = false
|
||||
fpc.surfaceView.shadowColor = .black
|
||||
fpc.surfaceView.shadowOffset = CGSize(width: 0, height: 16)
|
||||
fpc.surfaceView.shadowRadius = 16.0
|
||||
|
||||
// After:
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 8.0
|
||||
appearance.backgroundColor = .clear
|
||||
|
||||
let shadow = SurfaceAppearance.Shadow()
|
||||
shadow.color = .black
|
||||
shadow.offset = CGSize(width: 0, height: 16)
|
||||
shadow.radius = 16
|
||||
shadow.spread = 8
|
||||
appearance.shadows = [shadow]
|
||||
|
||||
fpc.surfaceView.appearance = appearance
|
||||
```
|
||||
|
||||
* These properties are changed for the top, left and right positioned panel.
|
||||
* `grabberTopPadding` is now `grabberHandlePadding`.
|
||||
* `topGrabberBarHeight` is now `grabberAreaOffset`.
|
||||
* `grabberHandleWidth` and `grabberHandleHeight` are replaced with `grabberHandleSize`.
|
||||
|
||||
## `BackdropView`
|
||||
|
||||
* The dismissal action of the backdrop is disabled by default.
|
||||
* You can enable it to set `BackdropView.dismissalTapGestureRecognizer.isEnabled` to `true`.
|
||||
|
||||
## `FloatingPanelPanGestureRecognizer`
|
||||
|
||||
* `delegateProxy` property is added to intercept the gesture recognizer delegate.
|
||||
|
||||
```swift
|
||||
func layoutPanelForPad() {
|
||||
fpc.behavior = SearchPaneliPadBehavior()
|
||||
fpc.panGestureRecognizer.delegateProxy = self
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
* `UISpringTimingParameters(decelerationRate:frequencyResponse:initialVelocity:)` initializer is added.
|
||||
* The directory structure and file names in the Xcode project changes.
|
||||
@@ -1,426 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
6467E8642699AC5F00565F4F /* SurfaceAppearance+phone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6467E8632699AC5F00565F4F /* SurfaceAppearance+phone.swift */; };
|
||||
6467E86A2699B19D00565F4F /* SearchPanelPhoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6467E8692699B19D00565F4F /* SearchPanelPhoneDelegate.swift */; };
|
||||
649A122926C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A122826C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift */; };
|
||||
649A122D26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A122C26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift */; };
|
||||
64A5B7232691323900BCAA05 /* MapsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B7222691323900BCAA05 /* MapsApp.swift */; };
|
||||
64A5B7252691323900BCAA05 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B7242691323900BCAA05 /* ContentView.swift */; };
|
||||
64A5B734269133DC00BCAA05 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A5B733269133DC00BCAA05 /* FloatingPanel.framework */; };
|
||||
64A5B735269133DC00BCAA05 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 64A5B733269133DC00BCAA05 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
64A5B738269134CA00BCAA05 /* VisualEffectBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B737269134CA00BCAA05 /* VisualEffectBlur.swift */; };
|
||||
64A5B73C2691469900BCAA05 /* FloatingPanelContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B73B2691469900BCAA05 /* FloatingPanelContentView.swift */; };
|
||||
64A5B73E269147DC00BCAA05 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B73D269147DC00BCAA05 /* SearchBar.swift */; };
|
||||
64A5B7402691532400BCAA05 /* ResultsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B73F2691532400BCAA05 /* ResultsList.swift */; };
|
||||
64A5B7422691541A00BCAA05 /* HostingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B7412691541A00BCAA05 /* HostingCell.swift */; };
|
||||
64F7E83126AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82C26AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift */; };
|
||||
64F7E83226AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82D26AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift */; };
|
||||
64F7E83326AD70EB00A0E0F7 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82E26AD70EB00A0E0F7 /* FloatingPanelView.swift */; };
|
||||
64F7E83426AD70EB00A0E0F7 /* View+floatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82F26AD70EB00A0E0F7 /* View+floatingPanel.swift */; };
|
||||
64F7E83526AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E83026AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
64A5B736269133DC00BCAA05 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
64A5B735269133DC00BCAA05 /* FloatingPanel.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
6467E8632699AC5F00565F4F /* SurfaceAppearance+phone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SurfaceAppearance+phone.swift"; sourceTree = "<group>"; };
|
||||
6467E8692699B19D00565F4F /* SearchPanelPhoneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchPanelPhoneDelegate.swift; sourceTree = "<group>"; };
|
||||
649A122826C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIHostingController+ignoreKeyboard.swift"; sourceTree = "<group>"; };
|
||||
649A122C26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelGrabberHandlePadding.swift"; sourceTree = "<group>"; };
|
||||
64A5B71F2691323900BCAA05 /* Maps-SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Maps-SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
64A5B7222691323900BCAA05 /* MapsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapsApp.swift; sourceTree = "<group>"; };
|
||||
64A5B7242691323900BCAA05 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
64A5B733269133DC00BCAA05 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
64A5B737269134CA00BCAA05 /* VisualEffectBlur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectBlur.swift; sourceTree = "<group>"; };
|
||||
64A5B73B2691469900BCAA05 /* FloatingPanelContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelContentView.swift; sourceTree = "<group>"; };
|
||||
64A5B73D269147DC00BCAA05 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
|
||||
64A5B73F2691532400BCAA05 /* ResultsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsList.swift; sourceTree = "<group>"; };
|
||||
64A5B7412691541A00BCAA05 /* HostingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingCell.swift; sourceTree = "<group>"; };
|
||||
64F7E82C26AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelContentInsetAdjustmentBehavior.swift"; sourceTree = "<group>"; };
|
||||
64F7E82D26AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelContentMode.swift"; sourceTree = "<group>"; };
|
||||
64F7E82E26AD70EB00A0E0F7 /* FloatingPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
|
||||
64F7E82F26AD70EB00A0E0F7 /* View+floatingPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanel.swift"; sourceTree = "<group>"; };
|
||||
64F7E83026AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelSurfaceAppearance.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
64A5B71C2691323900BCAA05 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
64A5B734269133DC00BCAA05 /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
64A5B7162691323900BCAA05 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
64A5B7212691323900BCAA05 /* Maps */,
|
||||
64A5B7202691323900BCAA05 /* Products */,
|
||||
64A5B732269133DC00BCAA05 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
64A5B7202691323900BCAA05 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
64A5B71F2691323900BCAA05 /* Maps-SwiftUI.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
64A5B7212691323900BCAA05 /* Maps */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
64F7E82B26AD70EB00A0E0F7 /* FloatingPanel */,
|
||||
64BE55702691BBB0006D98BD /* Representable */,
|
||||
64A5B7222691323900BCAA05 /* MapsApp.swift */,
|
||||
64A5B7242691323900BCAA05 /* ContentView.swift */,
|
||||
64A5B73B2691469900BCAA05 /* FloatingPanelContentView.swift */,
|
||||
6467E8692699B19D00565F4F /* SearchPanelPhoneDelegate.swift */,
|
||||
6467E8632699AC5F00565F4F /* SurfaceAppearance+phone.swift */,
|
||||
64A5B7412691541A00BCAA05 /* HostingCell.swift */,
|
||||
64A5B73F2691532400BCAA05 /* ResultsList.swift */,
|
||||
);
|
||||
path = Maps;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
64A5B732269133DC00BCAA05 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
64A5B733269133DC00BCAA05 /* FloatingPanel.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
64BE55702691BBB0006D98BD /* Representable */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
64A5B73D269147DC00BCAA05 /* SearchBar.swift */,
|
||||
64A5B737269134CA00BCAA05 /* VisualEffectBlur.swift */,
|
||||
);
|
||||
path = Representable;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
64F7E82B26AD70EB00A0E0F7 /* FloatingPanel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
64F7E82E26AD70EB00A0E0F7 /* FloatingPanelView.swift */,
|
||||
64F7E82F26AD70EB00A0E0F7 /* View+floatingPanel.swift */,
|
||||
64F7E82C26AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift */,
|
||||
64F7E82D26AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift */,
|
||||
64F7E83026AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift */,
|
||||
649A122C26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift */,
|
||||
649A122826C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift */,
|
||||
);
|
||||
path = FloatingPanel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
64A5B71E2691323900BCAA05 /* Maps-SwiftUI */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 64A5B72D2691323A00BCAA05 /* Build configuration list for PBXNativeTarget "Maps-SwiftUI" */;
|
||||
buildPhases = (
|
||||
64A5B71B2691323900BCAA05 /* Sources */,
|
||||
64A5B71C2691323900BCAA05 /* Frameworks */,
|
||||
64A5B71D2691323900BCAA05 /* Resources */,
|
||||
64A5B736269133DC00BCAA05 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Maps-SwiftUI";
|
||||
productName = "Maps-SwiftUI";
|
||||
productReference = 64A5B71F2691323900BCAA05 /* Maps-SwiftUI.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
64A5B7172691323900BCAA05 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1300;
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
64A5B71E2691323900BCAA05 = {
|
||||
CreatedOnToolsVersion = 13.0;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 64A5B71A2691323900BCAA05 /* Build configuration list for PBXProject "Maps-SwiftUI" */;
|
||||
compatibilityVersion = "Xcode 13.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 64A5B7162691323900BCAA05;
|
||||
productRefGroup = 64A5B7202691323900BCAA05 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
64A5B71E2691323900BCAA05 /* Maps-SwiftUI */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
64A5B71D2691323900BCAA05 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
64A5B71B2691323900BCAA05 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6467E86A2699B19D00565F4F /* SearchPanelPhoneDelegate.swift in Sources */,
|
||||
64A5B7422691541A00BCAA05 /* HostingCell.swift in Sources */,
|
||||
64A5B738269134CA00BCAA05 /* VisualEffectBlur.swift in Sources */,
|
||||
64A5B7402691532400BCAA05 /* ResultsList.swift in Sources */,
|
||||
6467E8642699AC5F00565F4F /* SurfaceAppearance+phone.swift in Sources */,
|
||||
64F7E83226AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift in Sources */,
|
||||
64A5B7252691323900BCAA05 /* ContentView.swift in Sources */,
|
||||
64A5B7232691323900BCAA05 /* MapsApp.swift in Sources */,
|
||||
64A5B73C2691469900BCAA05 /* FloatingPanelContentView.swift in Sources */,
|
||||
649A122D26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift in Sources */,
|
||||
64F7E83326AD70EB00A0E0F7 /* FloatingPanelView.swift in Sources */,
|
||||
64F7E83426AD70EB00A0E0F7 /* View+floatingPanel.swift in Sources */,
|
||||
64F7E83526AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift in Sources */,
|
||||
64F7E83126AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift in Sources */,
|
||||
64A5B73E269147DC00BCAA05 /* SearchBar.swift in Sources */,
|
||||
649A122926C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
64A5B72B2691323A00BCAA05 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
64A5B72C2691323A00BCAA05 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
64A5B72E2691323A00BCAA05 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "example.Maps-SwiftUI";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
64A5B72F2691323A00BCAA05 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "exmaple.Maps-SwiftUI";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
64A5B71A2691323900BCAA05 /* Build configuration list for PBXProject "Maps-SwiftUI" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
64A5B72B2691323A00BCAA05 /* Debug */,
|
||||
64A5B72C2691323A00BCAA05 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
64A5B72D2691323A00BCAA05 /* Build configuration list for PBXNativeTarget "Maps-SwiftUI" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
64A5B72E2691323A00BCAA05 /* Debug */,
|
||||
64A5B72F2691323A00BCAA05 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 64A5B7172691323900BCAA05 /* Project object */;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// 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
|
||||
let contentViewController = UIViewController()
|
||||
contentViewController.view.addSubview(hostingViewController.view)
|
||||
fpc.set(contentViewController: contentViewController)
|
||||
fpc.addPanel(toParent: parentViewController, animated: false)
|
||||
|
||||
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
let bottomConstraint = hostingViewController.view.bottomAnchor.constraint(equalTo: contentViewController.view.bottomAnchor)
|
||||
bottomConstraint.priority = .defaultHigh
|
||||
NSLayoutConstraint.activate([
|
||||
hostingViewController.view.topAnchor.constraint(equalTo: contentViewController.view.topAnchor),
|
||||
hostingViewController.view.leadingAnchor.constraint(equalTo: contentViewController.view.leadingAnchor),
|
||||
hostingViewController.view.trailingAnchor.constraint(equalTo: contentViewController.view.trailingAnchor),
|
||||
bottomConstraint
|
||||
])
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct FloatingPanelContentView: View {
|
||||
@State private var searchText = ""
|
||||
@State private var isShowingCancelButton = false
|
||||
var proxy: FloatingPanelProxy
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
searchBar
|
||||
resultsList
|
||||
}
|
||||
// 👇🏻 for the floating panel grabber handle.
|
||||
.padding(.top, 6)
|
||||
.background(
|
||||
VisualEffectBlur(blurStyle: .systemMaterial)
|
||||
// ⚠️ If the `VisualEffectBlur` view receives taps, it's going
|
||||
// to mess up with the whole panel and render it
|
||||
// non-interactive, make sure it never receives any taps.
|
||||
.allowsHitTesting(false)
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
var searchBar: some View {
|
||||
SearchBar(
|
||||
"Search for a place or address",
|
||||
text: $searchText,
|
||||
isShowingCancelButton: $isShowingCancelButton
|
||||
) { isFocused in
|
||||
proxy.move(to: isFocused ? .full : .half, animated: true)
|
||||
isShowingCancelButton = isFocused
|
||||
} onCancel: {
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
}
|
||||
|
||||
var resultsList: some View {
|
||||
ResultsList(onScrollViewCreated: proxy.track(scrollView:))
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// A `UITableViewCell` that accepts a SwiftUI view as its content.
|
||||
///
|
||||
/// Credits to https://noahgilmore.com/blog/swiftui-self-sizing-cells/ .
|
||||
public final class HostingCell<Content: View>: UITableViewCell {
|
||||
private let hostingController = UIHostingController<Content?>(
|
||||
rootView: nil,
|
||||
ignoresKeyboard: true
|
||||
)
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
hostingController.view.backgroundColor = nil
|
||||
backgroundColor = .clear
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func set(rootView: Content, parentController: UIViewController) {
|
||||
hostingController.rootView = rootView
|
||||
hostingController.view.invalidateIntrinsicContentSize()
|
||||
|
||||
let requiresControllerMove = hostingController.parent != parentController
|
||||
if requiresControllerMove {
|
||||
parentController.addChild(hostingController)
|
||||
}
|
||||
|
||||
if !contentView.subviews.contains(hostingController.view) {
|
||||
contentView.addSubview(hostingController.view)
|
||||
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addConstraints([
|
||||
hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
if requiresControllerMove {
|
||||
hostingController.didMove(toParent: parentController)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct MapsApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.floatingPanel(delegate: SearchPanelPhoneDelegate()) { proxy in
|
||||
FloatingPanelContentView(proxy: proxy)
|
||||
}
|
||||
.floatingPanelSurfaceAppearance(.phone)
|
||||
.floatingPanelContentMode(.fitToBounds)
|
||||
.floatingPanelContentInsetAdjustmentBehavior(.never)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// UIKit's `UISearchBar`brought to SwiftUI.
|
||||
public struct SearchBar: UIViewRepresentable {
|
||||
var title: String
|
||||
@Binding var text: String
|
||||
@Binding var isShowingCancelButton: Bool
|
||||
var onEditingChanged: (Bool) -> Void
|
||||
var onCancel: () -> Void
|
||||
|
||||
public init(
|
||||
_ title: String = "",
|
||||
text: Binding<String>,
|
||||
isShowingCancelButton: Binding<Bool>,
|
||||
onEditingChanged: @escaping (Bool) -> Void = { _ in },
|
||||
onCancel: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self._text = text
|
||||
self._isShowingCancelButton = isShowingCancelButton
|
||||
self.onEditingChanged = onEditingChanged
|
||||
self.onCancel = onCancel
|
||||
}
|
||||
|
||||
public func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
|
||||
let searchBar = UISearchBar(frame: .zero)
|
||||
searchBar.searchBarStyle = .minimal
|
||||
searchBar.isTranslucent = true
|
||||
searchBar.placeholder = title
|
||||
searchBar.delegate = context.coordinator
|
||||
searchBar.autocapitalizationType = .none
|
||||
return searchBar
|
||||
}
|
||||
|
||||
public func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
|
||||
uiView.text = text
|
||||
uiView.placeholder = title
|
||||
uiView.setShowsCancelButton(isShowingCancelButton, animated: true)
|
||||
}
|
||||
|
||||
public func makeCoordinator() -> SearchBar.Coordinator {
|
||||
Coordinator(parent: self)
|
||||
}
|
||||
|
||||
public class Coordinator: NSObject, UISearchBarDelegate {
|
||||
var parent: SearchBar
|
||||
|
||||
init(parent: SearchBar) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
parent.text = searchText
|
||||
}
|
||||
|
||||
public func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
|
||||
parent.onEditingChanged(true)
|
||||
}
|
||||
|
||||
public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||
searchBar.resignFirstResponder()
|
||||
}
|
||||
|
||||
public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
|
||||
parent.onEditingChanged(false)
|
||||
}
|
||||
|
||||
public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
parent.onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS, introduced: 13, deprecated: 15, message: "Use iOS 15 material API.")
|
||||
public struct VisualEffectBlur<Content: View>: UIViewRepresentable {
|
||||
var blurStyle: UIBlurEffect.Style = .systemMaterial
|
||||
var vibrancyStyle: UIVibrancyEffectStyle? = nil
|
||||
@ViewBuilder var content: Content
|
||||
|
||||
public func makeUIView(context: Context) -> UIVisualEffectView {
|
||||
context.coordinator.blurView
|
||||
}
|
||||
|
||||
public func updateUIView(_ view: UIVisualEffectView, context: Context) {
|
||||
context.coordinator.update(
|
||||
content: content,
|
||||
blurStyle: blurStyle,
|
||||
vibrancyStyle: vibrancyStyle
|
||||
)
|
||||
}
|
||||
|
||||
public func makeCoordinator() -> Coordinator {
|
||||
Coordinator(content: content)
|
||||
}
|
||||
|
||||
public class Coordinator {
|
||||
let blurView = UIVisualEffectView()
|
||||
let vibrancyView = UIVisualEffectView()
|
||||
let hostingController: UIHostingController<Content>
|
||||
|
||||
init(content: Content) {
|
||||
hostingController = UIHostingController(rootView: content)
|
||||
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
hostingController.view.backgroundColor = nil
|
||||
blurView.contentView.addSubview(vibrancyView)
|
||||
|
||||
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
vibrancyView.contentView.addSubview(hostingController.view)
|
||||
vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
}
|
||||
|
||||
func update(content: Content, blurStyle: UIBlurEffect.Style, vibrancyStyle: UIVibrancyEffectStyle?) {
|
||||
hostingController.rootView = content
|
||||
|
||||
let blurEffect = UIBlurEffect(style: blurStyle)
|
||||
blurView.effect = blurEffect
|
||||
|
||||
if let vibrancyStyle = vibrancyStyle {
|
||||
vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect, style: vibrancyStyle)
|
||||
} else {
|
||||
vibrancyView.effect = nil
|
||||
}
|
||||
|
||||
hostingController.view.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension VisualEffectBlur where Content == EmptyView {
|
||||
init(
|
||||
blurStyle: UIBlurEffect.Style = .systemMaterial,
|
||||
vibrancyStyle: UIVibrancyEffectStyle? = nil
|
||||
) {
|
||||
self.init(blurStyle: blurStyle, vibrancyStyle: vibrancyStyle) {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ResultsList: UIViewControllerRepresentable {
|
||||
var onScrollViewCreated: (_ scrollView: UIScrollView) -> Void
|
||||
|
||||
func makeUIViewController(
|
||||
context: Context
|
||||
) -> ResultsTableViewController {
|
||||
let rtvc = ResultsTableViewController()
|
||||
onScrollViewCreated(rtvc.tableView)
|
||||
return rtvc
|
||||
}
|
||||
|
||||
func updateUIViewController(
|
||||
_ uiViewController: ResultsTableViewController,
|
||||
context: Context
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
final class ResultsTableViewController: UITableViewController {
|
||||
private let reuseIdentifier = "HostingCell<ResultListCell>"
|
||||
|
||||
private enum Section: CaseIterable {
|
||||
case main
|
||||
}
|
||||
|
||||
private struct TableViewItem: Hashable {
|
||||
let color: Color
|
||||
let symbolName: String
|
||||
let title: String
|
||||
let description: String
|
||||
}
|
||||
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, TableViewItem>?
|
||||
|
||||
// MARK: Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.backgroundColor = nil
|
||||
tableView.register(HostingCell<ResultListCell>.self, forCellReuseIdentifier: reuseIdentifier)
|
||||
configureDataSource()
|
||||
}
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
private func configureDataSource() {
|
||||
dataSource = UITableViewDiffableDataSource
|
||||
<Section, TableViewItem>(tableView: tableView) { [weak self] tableView, _, tableItem -> UITableViewCell? in
|
||||
self?.tableView(tableView, cellForTableViewItem: tableItem)
|
||||
}
|
||||
tableView.dataSource = dataSource
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, TableViewItem>()
|
||||
snapshot.appendSections([.main])
|
||||
let results: [TableViewItem] = (1...100).map {
|
||||
TableViewItem(
|
||||
color: Color(red: 255 / 255.0, green: 94 / 255.0 , blue: 94 / 255.0),
|
||||
symbolName: "heart.fill",
|
||||
title: "Favorites",
|
||||
description: "\($0) Places"
|
||||
)
|
||||
}
|
||||
snapshot.appendItems(results, toSection: .main)
|
||||
dataSource?.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
private func tableView(
|
||||
_ tableView: UITableView,
|
||||
cellForTableViewItem tableViewItem: TableViewItem
|
||||
) -> UITableViewCell {
|
||||
let cell: HostingCell<ResultListCell> = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) as! HostingCell<ResultListCell>
|
||||
setupResultTableViewCell(
|
||||
cell,
|
||||
color: tableViewItem.color,
|
||||
symbolName: tableViewItem.symbolName,
|
||||
title: tableViewItem.title,
|
||||
description: tableViewItem.description
|
||||
)
|
||||
return cell
|
||||
}
|
||||
|
||||
private func setupResultTableViewCell(
|
||||
_ cell: HostingCell<ResultListCell>,
|
||||
color: Color,
|
||||
symbolName: String,
|
||||
title: String,
|
||||
description: String
|
||||
) {
|
||||
cell.set(
|
||||
rootView: ResultListCell(
|
||||
color: color,
|
||||
symbolName: symbolName,
|
||||
title: title,
|
||||
description: description
|
||||
),
|
||||
parentController: self
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
didSelectRowAt indexPath: IndexPath
|
||||
) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
struct ResultListCell: View {
|
||||
let color: Color
|
||||
let symbolName: String
|
||||
let title: String
|
||||
let description: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: symbolName)
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding(8)
|
||||
.background(Circle().fill(color))
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(title)
|
||||
.font(.system(size: 20, weight: .bold))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(description)
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import FloatingPanel
|
||||
import UIKit
|
||||
|
||||
final class SearchPanelPhoneDelegate: FloatingPanelControllerDelegate {
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
|
||||
if vc.state == .full {
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import FloatingPanel
|
||||
|
||||
extension FloatingPanel.SurfaceAppearance {
|
||||
static var phone: SurfaceAppearance {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerCurve = .continuous
|
||||
appearance.cornerRadius = 8.0
|
||||
appearance.backgroundColor = .clear
|
||||
return appearance
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,13 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
543844BD23D2BE2000D5EDE4 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 543844BC23D2BE2000D5EDE4 /* MapKit.framework */; };
|
||||
549A5F59244673FE0025F312 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549A5F58244673FE0025F312 /* SearchViewController.swift */; };
|
||||
549D23D2233C77D5008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */; };
|
||||
549D23D3233C77D5008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
54B5112A216C3D840033A6F3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51129216C3D840033A6F3 /* AppDelegate.swift */; };
|
||||
54B5112C216C3D840033A6F3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B5112B216C3D840033A6F3 /* MainViewController.swift */; };
|
||||
54B5112C216C3D840033A6F3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B5112B216C3D840033A6F3 /* ViewController.swift */; };
|
||||
54B5112F216C3D840033A6F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54B5112D216C3D840033A6F3 /* Main.storyboard */; };
|
||||
54B51131216C3D860033A6F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54B51130216C3D860033A6F3 /* Assets.xcassets */; };
|
||||
54B51134216C3D860033A6F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */; };
|
||||
54E26CB624A989090066C720 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E26CB524A989090066C720 /* Utils.swift */; };
|
||||
54E26CB824A98E310066C720 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E26CB724A98E310066C720 /* DetailViewController.swift */; };
|
||||
5D82A6A728D18422006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6A628D1841E006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
54B5113F216C407F0033A6F3 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113E216C407F0033A6F3 /* FloatingPanel.framework */; };
|
||||
54B51140216C407F0033A6F3 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113E216C407F0033A6F3 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -28,7 +23,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
549D23D3233C77D5008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
|
||||
54B51140216C407F0033A6F3 /* FloatingPanel.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -36,19 +31,14 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
543844BC23D2BE2000D5EDE4 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
|
||||
549A5F58244673FE0025F312 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
|
||||
549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54B51126216C3D840033A6F3 /* Maps.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Maps.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54B51129216C3D840033A6F3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
54B5112B216C3D840033A6F3 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
||||
54B5112B216C3D840033A6F3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
54B5112E216C3D840033A6F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
54B51130216C3D860033A6F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
54B51133216C3D860033A6F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
54B51135216C3D860033A6F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
54E26CB524A989090066C720 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
|
||||
54E26CB724A98E310066C720 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
|
||||
5D82A6A628D1841E006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
|
||||
54B5113E216C407F0033A6F3 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -56,31 +46,19 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
543844BD23D2BE2000D5EDE4 /* MapKit.framework in Frameworks */,
|
||||
5D82A6A728D18422006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
549D23D2233C77D5008EF4D7 /* FloatingPanel.framework in Frameworks */,
|
||||
54B5113F216C407F0033A6F3 /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
543844BB23D2BE1F00D5EDE4 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D82A6A628D1841E006A44BA /* libswiftCoreGraphics.tbd */,
|
||||
543844BC23D2BE2000D5EDE4 /* MapKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54B5111D216C3D840033A6F3 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */,
|
||||
54B5113E216C407F0033A6F3 /* FloatingPanel.framework */,
|
||||
54B51128216C3D840033A6F3 /* Maps */,
|
||||
54B51127216C3D840033A6F3 /* Products */,
|
||||
543844BB23D2BE1F00D5EDE4 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -95,14 +73,11 @@
|
||||
54B51128216C3D840033A6F3 /* Maps */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54B51129216C3D840033A6F3 /* AppDelegate.swift */,
|
||||
54B5112B216C3D840033A6F3 /* ViewController.swift */,
|
||||
54B5112D216C3D840033A6F3 /* Main.storyboard */,
|
||||
54B51130216C3D860033A6F3 /* Assets.xcassets */,
|
||||
54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */,
|
||||
54B5112D216C3D840033A6F3 /* Main.storyboard */,
|
||||
54B51129216C3D840033A6F3 /* AppDelegate.swift */,
|
||||
54B5112B216C3D840033A6F3 /* MainViewController.swift */,
|
||||
549A5F58244673FE0025F312 /* SearchViewController.swift */,
|
||||
54E26CB724A98E310066C720 /* DetailViewController.swift */,
|
||||
54E26CB524A989090066C720 /* Utils.swift */,
|
||||
54B51135216C3D860033A6F3 /* Info.plist */,
|
||||
);
|
||||
path = Maps;
|
||||
@@ -136,7 +111,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1000;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = scenee;
|
||||
TargetAttributes = {
|
||||
54B51125216C3D840033A6F3 = {
|
||||
@@ -180,11 +155,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
549A5F59244673FE0025F312 /* SearchViewController.swift in Sources */,
|
||||
54B5112C216C3D840033A6F3 /* MainViewController.swift in Sources */,
|
||||
54E26CB824A98E310066C720 /* DetailViewController.swift in Sources */,
|
||||
54B5112C216C3D840033A6F3 /* ViewController.swift in Sources */,
|
||||
54B5112A216C3D840033A6F3 /* AppDelegate.swift in Sources */,
|
||||
54E26CB624A989090066C720 /* Utils.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -236,7 +208,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -298,7 +269,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -335,16 +305,12 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Maps/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -358,16 +324,12 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Maps/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -27,6 +27,8 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@@ -36,8 +38,8 @@
|
||||
ReferencedContainer = "container:Maps.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -61,6 +63,8 @@
|
||||
ReferencedContainer = "container:Maps.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// Maps
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/10/09.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina5_9" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Main View Controller-->
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="MainViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="5Jw-n2-Cpw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
</mapView>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d9i-3g-8Ja">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="lMa-xa-AVV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<blurEffect style="prominent"/>
|
||||
<blurEffect style="light"/>
|
||||
</visualEffectView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="5Jw-n2-Cpw" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailing" id="1Fg-Be-qfh"/>
|
||||
@@ -38,6 +41,7 @@
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="top" secondItem="d9i-3g-8Ja" secondAttribute="bottom" id="Y1G-hr-aEX"/>
|
||||
<constraint firstItem="d9i-3g-8Ja" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="diB-Ij-HN3"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="mapView" destination="5Jw-n2-Cpw" id="WVC-rU-mLe"/>
|
||||
@@ -47,36 +51,36 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="136.80000000000001" y="133.00492610837438"/>
|
||||
</scene>
|
||||
<!--Search View Controller-->
|
||||
<!--Search Panel View Controller-->
|
||||
<scene sceneID="kXy-li-p3C">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="SearchViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0S1-Lk-JgE" customClass="SearchViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="SearchPanel" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0S1-Lk-JgE" customClass="SearchPanelViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ncl-E9-yRn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ye3-uU-bq3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="900"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="ED1-gT-FBj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="900"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<searchBar contentMode="redraw" searchBarStyle="minimal" translatesAutoresizingMaskIntoConstraints="NO" id="Zcj-SE-gb8">
|
||||
<rect key="frame" x="0.0" y="6" width="600" height="51"/>
|
||||
<rect key="frame" x="0.0" y="6" width="375" height="56"/>
|
||||
<textInputTraits key="textInputTraits"/>
|
||||
</searchBar>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="D7r-re-InH">
|
||||
<rect key="frame" x="0.0" y="61" width="600" height="539"/>
|
||||
<rect key="frame" x="0.0" y="66" width="375" height="748"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<view key="tableHeaderView" contentMode="scaleToFill" id="u28-LY-hIh" customClass="SearchHeaderView" customModule="Maps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="116"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="116"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" baselineRelativeArrangement="YES" translatesAutoresizingMaskIntoConstraints="NO" id="era-8w-yA1">
|
||||
<rect key="frame" x="24" y="10.5" width="552" height="97.5"/>
|
||||
<rect key="frame" x="24" y="10.666666666666664" width="327" height="97.333333333333343"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="auI-1v-Yfk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="food" translatesAutoresizingMaskIntoConstraints="NO" id="ErN-bC-qTx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -86,7 +90,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Food & Drinks" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nx2-fW-xAm">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -94,7 +98,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="0vd-sD-XKv">
|
||||
<rect key="frame" x="164" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="89" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="shopping" translatesAutoresizingMaskIntoConstraints="NO" id="xcm-St-HAo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -104,7 +108,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="H7q-q2-ga5">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<string key="text">Shopping
|
||||
</string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
@@ -114,7 +118,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Jd8-YL-b5s">
|
||||
<rect key="frame" x="328" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="178" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="fun" translatesAutoresizingMaskIntoConstraints="NO" id="bMJ-Jn-Gi8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -124,7 +128,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kKh-45-FZ2">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<string key="text">Fun
|
||||
</string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
@@ -134,7 +138,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="dTL-e1-Arz">
|
||||
<rect key="frame" x="492" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="267" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="travel" translatesAutoresizingMaskIntoConstraints="NO" id="8h3-fo-pC3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -144,7 +148,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WBT-Vj-7QA">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<string key="text">Travel
|
||||
</string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
@@ -166,10 +170,10 @@
|
||||
</view>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="Cell" rowHeight="70" id="LzC-B9-Adb" customClass="SearchCell" customModule="Maps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="160.5" width="600" height="70"/>
|
||||
<rect key="frame" x="0.0" y="144" width="375" height="70"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LzC-B9-Adb" id="evr-60-laS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="70"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="69.666666666666671"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="like" translatesAutoresizingMaskIntoConstraints="NO" id="GEk-yE-lLq">
|
||||
@@ -181,16 +185,16 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="Gfl-Oy-rsy">
|
||||
<rect key="frame" x="57" y="12" width="528" height="46"/>
|
||||
<rect key="frame" x="57" y="11.999999999999996" width="303" height="45.666666666666657"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Favorites" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Spf-8L-Ne6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="528" height="22"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="303" height="22"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0 Places" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gyo-3V-7U8">
|
||||
<rect key="frame" x="0.0" y="24" width="528" height="22"/>
|
||||
<rect key="frame" x="0.0" y="24" width="303" height="21.666666666666671"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<color key="textColor" red="0.57647058819999997" green="0.57647058819999997" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -223,22 +227,22 @@
|
||||
<constraint firstAttribute="trailing" secondItem="D7r-re-InH" secondAttribute="trailing" id="BES-GA-Btp"/>
|
||||
<constraint firstItem="D7r-re-InH" firstAttribute="leading" secondItem="ED1-gT-FBj" secondAttribute="leading" id="UTe-YL-17h"/>
|
||||
<constraint firstItem="Zcj-SE-gb8" firstAttribute="top" secondItem="ED1-gT-FBj" secondAttribute="top" constant="6" id="apU-Pd-PEO"/>
|
||||
<constraint firstAttribute="bottom" secondItem="D7r-re-InH" secondAttribute="bottom" id="vfS-Lx-TXz"/>
|
||||
<constraint firstAttribute="bottom" secondItem="D7r-re-InH" secondAttribute="bottom" constant="86" id="vfS-Lx-TXz"/>
|
||||
<constraint firstItem="D7r-re-InH" firstAttribute="top" secondItem="Zcj-SE-gb8" secondAttribute="bottom" constant="4" id="vro-cd-B9c"/>
|
||||
<constraint firstItem="Zcj-SE-gb8" firstAttribute="leading" secondItem="ED1-gT-FBj" secondAttribute="leading" id="wMb-L2-Z0W"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<blurEffect style="prominent"/>
|
||||
<blurEffect style="extraLight"/>
|
||||
</visualEffectView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="G74-X7-Za8"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Ye3-uU-bq3" firstAttribute="leading" secondItem="G74-X7-Za8" secondAttribute="leading" id="Kr2-sU-ZWZ"/>
|
||||
<constraint firstItem="Ye3-uU-bq3" firstAttribute="bottom" secondItem="Ncl-E9-yRn" secondAttribute="bottom" id="aWM-s3-3o4"/>
|
||||
<constraint firstItem="Ye3-uU-bq3" firstAttribute="bottom" secondItem="Ncl-E9-yRn" secondAttribute="bottom" constant="88" id="aWM-s3-3o4"/>
|
||||
<constraint firstItem="Ye3-uU-bq3" firstAttribute="trailing" secondItem="G74-X7-Za8" secondAttribute="trailing" id="fEL-8y-Acc"/>
|
||||
<constraint firstItem="Ye3-uU-bq3" firstAttribute="top" secondItem="Ncl-E9-yRn" secondAttribute="top" id="w77-ba-FrJ"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="G74-X7-Za8"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="searchBar" destination="Zcj-SE-gb8" id="BH7-Gy-RG5"/>
|
||||
@@ -250,44 +254,6 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="789.60000000000002" y="133.5832083958021"/>
|
||||
</scene>
|
||||
<!--Detail View Controller-->
|
||||
<scene sceneID="5tH-Ya-PzB">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="DetailViewController" id="Tp2-MF-IFz" customClass="DetailViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="FmO-AT-4Y7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c3d-2e-0b1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="9fL-a5-0LS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="kP7-56-wlG">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="kP7-56-wlG" firstAttribute="leading" secondItem="9fL-a5-0LS" secondAttribute="leading" id="6gf-GO-cnv"/>
|
||||
<constraint firstAttribute="bottom" secondItem="kP7-56-wlG" secondAttribute="bottom" id="WrH-tz-UQF"/>
|
||||
<constraint firstItem="kP7-56-wlG" firstAttribute="top" secondItem="9fL-a5-0LS" secondAttribute="top" id="aJk-bz-lVc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="kP7-56-wlG" secondAttribute="trailing" id="sE0-9V-Rot"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<blurEffect style="extraLight"/>
|
||||
</visualEffectView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="ctv-Dd-JUc"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="EDp-D2-xcT" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1447" y="133"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="food" width="60" height="60"/>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
|
||||
class DetailViewController: UIViewController {
|
||||
var item: LocationItem?
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
import FloatingPanel
|
||||
|
||||
class MainViewController: UIViewController {
|
||||
typealias PanelDelegate = FloatingPanelControllerDelegate & UIGestureRecognizerDelegate
|
||||
|
||||
// Search Panel
|
||||
lazy var fpc = FloatingPanelController()
|
||||
lazy var fpcDelegate: PanelDelegate =
|
||||
(traitCollection.userInterfaceIdiom == .pad) ? SearchPanelPadDelegate(owner: self) : SearchPanelPhoneDelegate(owner: self)
|
||||
lazy var searchVC =
|
||||
storyboard?.instantiateViewController(withIdentifier: "SearchViewController") as! SearchViewController
|
||||
|
||||
// Detail Panel
|
||||
lazy var detailFpc = FloatingPanelController()
|
||||
lazy var detailFpcDelegate: PanelDelegate =
|
||||
(traitCollection.userInterfaceIdiom == .pad) ? DetailPanelPadDelegate(owner: self) : DetailPanelPhoneDelegate(owner: self)
|
||||
lazy var detailVC =
|
||||
storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
|
||||
|
||||
@IBOutlet weak var mapView: MKMapView!
|
||||
}
|
||||
|
||||
extension MainViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
fpc.contentMode = .fitToBounds
|
||||
fpc.delegate = fpcDelegate
|
||||
fpc.set(contentViewController: searchVC)
|
||||
fpc.track(scrollView: searchVC.tableView)
|
||||
|
||||
detailFpc.isRemovalInteractionEnabled = true
|
||||
detailFpc.set(contentViewController: detailVC)
|
||||
|
||||
switch traitCollection.userInterfaceIdiom {
|
||||
case .pad:
|
||||
layoutPanelForPad()
|
||||
default:
|
||||
layoutPanelForPhone()
|
||||
}
|
||||
|
||||
setUpMapView()
|
||||
setUpSearchView()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
// Must be here
|
||||
searchVC.searchBar.delegate = self
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
tearDownMapView()
|
||||
}
|
||||
}
|
||||
|
||||
extension MainViewController {
|
||||
func layoutPanelForPad() {
|
||||
fpc.behavior = SearchPaneliPadBehavior()
|
||||
fpc.panGestureRecognizer.delegateProxy = fpcDelegate
|
||||
|
||||
// Not use addPanel(toParent:) because of the Auto Layout configuration of fpc.view.
|
||||
view.addSubview(fpc.view)
|
||||
addChild(fpc)
|
||||
fpc.view.frame = view.bounds // Needed for a correct safe area configuration
|
||||
fpc.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
fpc.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
|
||||
fpc.view.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0 ),
|
||||
fpc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
|
||||
fpc.view.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
])
|
||||
fpc.show(animated: false) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.didMove(toParent: self)
|
||||
}
|
||||
|
||||
[fpc, detailFpc].forEach { $0.setAppearanceForPad() }
|
||||
}
|
||||
|
||||
func layoutPanelForPhone() {
|
||||
fpc.track(scrollView: searchVC.tableView) // Only track the table view on iPhone
|
||||
fpc.addPanel(toParent: self, animated: true)
|
||||
|
||||
[fpc, detailFpc].forEach { $0.setAppearanceForPhone()}
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatingPanelController {
|
||||
func setAppearanceForPhone() {
|
||||
let appearance = SurfaceAppearance()
|
||||
if #available(iOS 13.0, *) {
|
||||
appearance.cornerCurve = .continuous
|
||||
}
|
||||
appearance.cornerRadius = 8.0
|
||||
appearance.backgroundColor = .clear
|
||||
surfaceView.appearance = appearance
|
||||
}
|
||||
|
||||
func setAppearanceForPad() {
|
||||
view.clipsToBounds = false
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 8.0
|
||||
let shadow = SurfaceAppearance.Shadow()
|
||||
shadow.color = UIColor.black
|
||||
shadow.offset = CGSize(width: 0, height: 16)
|
||||
shadow.radius = 16
|
||||
shadow.spread = 8
|
||||
appearance.shadows = [shadow]
|
||||
appearance.backgroundColor = .clear
|
||||
surfaceView.appearance = appearance
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UISearchBarDelegate
|
||||
|
||||
extension MainViewController: UISearchBarDelegate {
|
||||
func activate(searchBar: UISearchBar) {
|
||||
searchBar.showsCancelButton = true
|
||||
searchVC.showHeader(animated: true)
|
||||
searchVC.tableView.alpha = 1.0
|
||||
detailVC.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
func deactivate(searchBar: UISearchBar) {
|
||||
searchBar.resignFirstResponder()
|
||||
searchBar.showsCancelButton = false
|
||||
searchVC.hideHeader(animated: true)
|
||||
}
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
deactivate(searchBar: searchBar)
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.fpc.move(to: .half, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
|
||||
activate(searchBar: searchBar)
|
||||
UIView.animate(withDuration: 0.25) { [weak self] in
|
||||
self?.fpc.move(to: .full, animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - iPhone
|
||||
|
||||
class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
|
||||
unowned let owner: MainViewController
|
||||
|
||||
init(owner: MainViewController) {
|
||||
self.owner = owner
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
switch newCollection.verticalSizeClass {
|
||||
case .compact:
|
||||
let appearance = vc.surfaceView.appearance
|
||||
appearance.borderWidth = 1.0 / owner.traitCollection.displayScale
|
||||
appearance.borderColor = UIColor.black.withAlphaComponent(0.2)
|
||||
vc.surfaceView.appearance = appearance
|
||||
return SearchPanelLandscapeLayout()
|
||||
default:
|
||||
let appearance = vc.surfaceView.appearance
|
||||
appearance.borderWidth = 0.0
|
||||
appearance.borderColor = nil
|
||||
vc.surfaceView.appearance = appearance
|
||||
return FloatingPanelBottomLayout()
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) {
|
||||
debugPrint("surfaceLocation: ", vc.surfaceLocation)
|
||||
let loc = vc.surfaceLocation
|
||||
|
||||
if vc.isAttracting == false {
|
||||
let minY = vc.surfaceLocation(for: .full).y - 6.0
|
||||
let maxY = vc.surfaceLocation(for: .tip).y + 6.0
|
||||
vc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY))
|
||||
}
|
||||
|
||||
let tipY = vc.surfaceLocation(for: .tip).y
|
||||
if loc.y > tipY - 44.0 {
|
||||
let progress = max(0.0, min((tipY - loc.y) / 44.0, 1.0))
|
||||
owner.searchVC.tableView.alpha = progress
|
||||
} else {
|
||||
owner.searchVC.tableView.alpha = 1.0
|
||||
}
|
||||
debugPrint("NearbyState : ",vc.nearbyState)
|
||||
}
|
||||
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
|
||||
if vc.state == .full {
|
||||
owner.searchVC.searchBar.showsCancelButton = false
|
||||
owner.searchVC.searchBar.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
|
||||
if targetState.pointee != .full {
|
||||
owner.searchVC.hideHeader(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SearchPanelLandscapeLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .tip
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
if #available(iOS 11.0, *) {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
}
|
||||
}
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
class DetailPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
|
||||
unowned let owner: MainViewController
|
||||
|
||||
init(owner: MainViewController) {
|
||||
self.owner = owner
|
||||
}
|
||||
}
|
||||
|
||||
class DetailPanelPhoneLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
let initialState: FloatingPanelState = .full
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - iPad
|
||||
|
||||
class SearchPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
|
||||
unowned let owner: MainViewController
|
||||
|
||||
init(owner: MainViewController) {
|
||||
self.owner = owner
|
||||
}
|
||||
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
if newCollection.horizontalSizeClass == .compact {
|
||||
fpc.surfaceView.containerMargins = .zero
|
||||
return FloatingPanelBottomLayout()
|
||||
}
|
||||
fpc.surfaceView.containerMargins = UIEdgeInsets(top: .leastNonzeroMagnitude, // For top left/right rounding corners
|
||||
left: 16,
|
||||
bottom: 0.0,
|
||||
right: 0.0)
|
||||
return SearchPanelPadLayout()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
|
||||
if vc.state == .full {
|
||||
owner.searchVC.searchBar.showsCancelButton = false
|
||||
owner.searchVC.searchBar.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
|
||||
if targetState.pointee != .full {
|
||||
owner.searchVC.hideHeader(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SearchPanelPadLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .top
|
||||
let initialState: FloatingPanelState = .tip
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 80.0, edge: .top, referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 200.0, edge: .top, referenceGuide: .superview),
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 60.0, edge: .bottom, referenceGuide: .superview),
|
||||
]
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 375),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class SearchPaneliPadBehavior: FloatingPanelBehavior {
|
||||
var springDecelerationRate: CGFloat {
|
||||
return UIScrollView.DecelerationRate.fast.rawValue - 0.003
|
||||
}
|
||||
var springResponseTime: CGFloat {
|
||||
return 0.3
|
||||
}
|
||||
var momentumProjectionRate: CGFloat {
|
||||
return UIScrollView.DecelerationRate.fast.rawValue
|
||||
}
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class DetailPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
|
||||
unowned let owner: MainViewController
|
||||
|
||||
init(owner: MainViewController) {
|
||||
self.owner = owner
|
||||
}
|
||||
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
if newCollection.horizontalSizeClass == .compact {
|
||||
fpc.surfaceView.containerMargins = .zero
|
||||
return FloatingPanelBottomLayout()
|
||||
}
|
||||
if let item = owner.detailVC.item, item.title.contains("Right") {
|
||||
fpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: .leastNonzeroMagnitude)
|
||||
return DetailPanelPadRightLayout()
|
||||
}
|
||||
fpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: .leastNonzeroMagnitude, bottom: 0.0, right: 0.0)
|
||||
return DetailPanelPadLeftLayout()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class DetailPanelPadLeftLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .left
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 375, edge: .left, referenceGuide: .superview)
|
||||
]
|
||||
let initialState: FloatingPanelState = .full
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
class DetailPanelPadRightLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .right
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 375, edge: .right, referenceGuide: .superview)
|
||||
]
|
||||
let initialState: FloatingPanelState = .full
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MKMapViewDelegate
|
||||
|
||||
extension MainViewController: MKMapViewDelegate {
|
||||
func setUpMapView() {
|
||||
let center = CLLocationCoordinate2D(latitude: 37.623198015869235,
|
||||
longitude: -122.43066818432008)
|
||||
let span = MKCoordinateSpan(latitudeDelta: 0.4425100023575723,
|
||||
longitudeDelta: 0.28543697435880233)
|
||||
let region = MKCoordinateRegion(center: center, span: span)
|
||||
mapView.region = region
|
||||
mapView.showsCompass = true
|
||||
mapView.showsUserLocation = true
|
||||
mapView.delegate = self
|
||||
}
|
||||
|
||||
func tearDownMapView() {
|
||||
// Prevent a crash
|
||||
mapView.delegate = nil
|
||||
mapView = nil
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
|
||||
extension MainViewController: UITableViewDelegate {
|
||||
func setUpSearchView() {
|
||||
searchVC.loadViewIfNeeded()
|
||||
searchVC.tableView.delegate = self
|
||||
searchVC.searchBar.placeholder = "Search for a place or address"
|
||||
let isPad = (traitCollection.userInterfaceIdiom == .pad)
|
||||
searchVC.items = [
|
||||
.init(mark: "mark", title: "Marked Location" + (isPad ? " (Left panel)" : ""), subtitle: "Golden Gate Bridge, San Francisco"),
|
||||
.init(mark: "mark", title: "Marked Location" + (isPad ? " (Right panel)" : ""), subtitle: "San Francisco Museum of Modern Art"),
|
||||
]
|
||||
searchVC.items.append(contentsOf: (0...98).map {
|
||||
.init(mark: "like", title: "Favorites", subtitle: "\($0) Places")
|
||||
})
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
deactivate(searchBar: searchVC.searchBar)
|
||||
|
||||
// Show a detail panel
|
||||
switch indexPath.row {
|
||||
case 0:
|
||||
detailVC.item = searchVC.items[safe: 0]
|
||||
|
||||
// Show detail vc in the left positioned panel
|
||||
switch traitCollection.userInterfaceIdiom {
|
||||
case .pad:
|
||||
detailFpc.layout = DetailPanelPadLeftLayout()
|
||||
detailFpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: 16.0, bottom: 0.0, right: 0.0)
|
||||
default:
|
||||
detailFpc.layout = DetailPanelPhoneLayout()
|
||||
detailFpc.surfaceView.containerMargins = .zero
|
||||
}
|
||||
detailFpc.addPanel(toParent: self, animated: true)
|
||||
case 1:
|
||||
detailVC.item = searchVC.items[safe: 1]
|
||||
|
||||
// Show detail vc in the right positioned panel
|
||||
switch traitCollection.userInterfaceIdiom {
|
||||
case .pad:
|
||||
detailFpc.layout = DetailPanelPadRightLayout()
|
||||
detailFpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0)
|
||||
default:
|
||||
detailFpc.layout = DetailPanelPhoneLayout()
|
||||
detailFpc.surfaceView.containerMargins = .zero
|
||||
}
|
||||
detailFpc.addPanel(toParent: self, animated: true)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Models
|
||||
|
||||
struct LocationItem {
|
||||
let mark: String
|
||||
let title: String
|
||||
let subtitle: String
|
||||
|
||||
init(mark: String, title: String, subtitle: String) {
|
||||
self.mark = mark
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
class SearchViewController: UIViewController, UITableViewDataSource {
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var searchBar: UISearchBar!
|
||||
@IBOutlet weak var visualEffectView: UIVisualEffectView!
|
||||
|
||||
var items: [LocationItem] = []
|
||||
|
||||
// For iOS 10 only
|
||||
private lazy var shadowLayer: CAShapeLayer = CAShapeLayer()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.dataSource = self
|
||||
searchBar.setSearchText(fontSize: 15.0)
|
||||
|
||||
hideHeader(animated: false)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11, *) {
|
||||
} else {
|
||||
// Exmaple: Add rounding corners on iOS 10
|
||||
visualEffectView.layer.cornerRadius = 9.0
|
||||
visualEffectView.clipsToBounds = true
|
||||
|
||||
// Exmaple: Add shadow manually on iOS 10
|
||||
view.layer.insertSublayer(shadowLayer, at: 0)
|
||||
let rect = visualEffectView.frame
|
||||
let path = UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.topLeft, .topRight],
|
||||
cornerRadii: CGSize(width: 9.0, height: 9.0))
|
||||
shadowLayer.frame = visualEffectView.frame
|
||||
shadowLayer.shadowPath = path.cgPath
|
||||
shadowLayer.shadowColor = UIColor.black.cgColor
|
||||
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0)
|
||||
shadowLayer.shadowOpacity = 0.2
|
||||
shadowLayer.shadowRadius = 3.0
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return items.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
if let cell = cell as? SearchCell, let item = items[safe: indexPath.row] {
|
||||
cell.iconImageView.image = UIImage(named: item.mark)
|
||||
cell.titleLabel.text = item.title
|
||||
cell.subTitleLabel.text = item.subtitle
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
func showHeader(animated: Bool) {
|
||||
changeHeader(height: 116.0, aniamted: animated)
|
||||
}
|
||||
|
||||
func hideHeader(animated: Bool) {
|
||||
changeHeader(height: 0.0, aniamted: animated)
|
||||
}
|
||||
|
||||
private func changeHeader(height: CGFloat, aniamted: Bool) {
|
||||
guard let headerView = tableView.tableHeaderView, headerView.bounds.height != height else { return }
|
||||
if aniamted == false {
|
||||
updateHeader(height: height)
|
||||
return
|
||||
}
|
||||
tableView.beginUpdates()
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.updateHeader(height: height)
|
||||
}
|
||||
tableView.endUpdates()
|
||||
}
|
||||
|
||||
private func updateHeader(height: CGFloat) {
|
||||
guard let headerView = tableView.tableHeaderView else { return }
|
||||
var frame = headerView.frame
|
||||
frame.size.height = height
|
||||
self.tableView.tableHeaderView?.frame = frame
|
||||
}
|
||||
}
|
||||
|
||||
class SearchCell: UITableViewCell {
|
||||
@IBOutlet weak var iconImageView: UIImageView!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var subTitleLabel: UILabel!
|
||||
}
|
||||
|
||||
class SearchHeaderView: UIView {
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
|
||||
extension UISearchBar {
|
||||
func setSearchText(fontSize: CGFloat) {
|
||||
if #available(iOS 13, *) {
|
||||
let font = searchTextField.font
|
||||
searchTextField.font = font?.withSize(fontSize)
|
||||
} else {
|
||||
let textField = value(forKey: "_searchField") as! UITextField
|
||||
textField.font = textField.font?.withSize(fontSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension Collection {
|
||||
subscript (safe index: Index) -> Element? {
|
||||
return indices.contains(index) ? self[index] : nil
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
var isLandscape: Bool {
|
||||
if #available(iOS 13.0, *) {
|
||||
return view.window?.windowScene?.interfaceOrientation.isLandscape ?? false
|
||||
} else {
|
||||
return UIApplication.shared.statusBarOrientation.isLandscape
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
import FloatingPanel
|
||||
|
||||
class ViewController: UIViewController, MKMapViewDelegate, UISearchBarDelegate, FloatingPanelControllerDelegate {
|
||||
var fpc: FloatingPanelController!
|
||||
var searchVC: SearchPanelViewController!
|
||||
|
||||
@IBOutlet weak var mapView: MKMapView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
// Initialize FloatingPanelController
|
||||
fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
|
||||
// Initialize FloatingPanelController and add the view
|
||||
fpc.surfaceView.backgroundColor = .clear
|
||||
if #available(iOS 11, *) {
|
||||
fpc.surfaceView.cornerRadius = 9.0
|
||||
} else {
|
||||
fpc.surfaceView.cornerRadius = 0.0
|
||||
}
|
||||
fpc.surfaceView.shadowHidden = false
|
||||
|
||||
searchVC = storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as? SearchPanelViewController
|
||||
|
||||
// Set a content view controller
|
||||
fpc.set(contentViewController: searchVC)
|
||||
fpc.track(scrollView: searchVC.tableView)
|
||||
|
||||
setupMapView()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
// Add FloatingPanel to a view with animation.
|
||||
fpc.addPanel(toParent: self, animated: true)
|
||||
|
||||
// Must be here
|
||||
searchVC.searchBar.delegate = self
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
teardownMapView()
|
||||
}
|
||||
|
||||
func setupMapView() {
|
||||
let center = CLLocationCoordinate2D(latitude: 37.623198015869235,
|
||||
longitude: -122.43066818432008)
|
||||
let span = MKCoordinateSpan(latitudeDelta: 0.4425100023575723,
|
||||
longitudeDelta: 0.28543697435880233)
|
||||
let region = MKCoordinateRegion(center: center, span: span)
|
||||
mapView.region = region
|
||||
mapView.showsCompass = true
|
||||
mapView.showsUserLocation = true
|
||||
mapView.delegate = self
|
||||
}
|
||||
|
||||
func teardownMapView() {
|
||||
// Prevent a crash
|
||||
mapView.delegate = nil
|
||||
mapView = nil
|
||||
}
|
||||
|
||||
// MARK: UISearchBarDelegate
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
searchBar.resignFirstResponder()
|
||||
searchBar.showsCancelButton = false
|
||||
searchVC.hideHeader()
|
||||
fpc.move(to: .half, animated: true)
|
||||
}
|
||||
|
||||
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
|
||||
searchBar.showsCancelButton = true
|
||||
searchVC.showHeader()
|
||||
searchVC.tableView.alpha = 1.0
|
||||
fpc.move(to: .full, animated: true)
|
||||
}
|
||||
|
||||
// MARK: FloatingPanelControllerDelegate
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
switch newCollection.verticalSizeClass {
|
||||
case .compact:
|
||||
fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale
|
||||
fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2)
|
||||
return SearchPanelLandscapeLayout()
|
||||
default:
|
||||
fpc.surfaceView.borderWidth = 0.0
|
||||
fpc.surfaceView.borderColor = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) {
|
||||
let y = vc.surfaceView.frame.origin.y
|
||||
let tipY = vc.originYOfSurface(for: .tip)
|
||||
if y > tipY - 44.0 {
|
||||
let progress = max(0.0, min((tipY - y) / 44.0, 1.0))
|
||||
self.searchVC.tableView.alpha = progress
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
|
||||
if vc.position == .full {
|
||||
searchVC.searchBar.showsCancelButton = false
|
||||
searchVC.searchBar.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {
|
||||
if targetPosition != .full {
|
||||
searchVC.hideHeader()
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: 0.25,
|
||||
delay: 0.0,
|
||||
options: .allowUserInteraction,
|
||||
animations: {
|
||||
if targetPosition == .tip {
|
||||
self.searchVC.tableView.alpha = 0.0
|
||||
} else {
|
||||
self.searchVC.tableView.alpha = 1.0
|
||||
}
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
class SearchPanelViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var searchBar: UISearchBar!
|
||||
@IBOutlet weak var visualEffectView: UIVisualEffectView!
|
||||
|
||||
// For iOS 10 only
|
||||
private lazy var shadowLayer: CAShapeLayer = CAShapeLayer()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
searchBar.placeholder = "Search for a place or address"
|
||||
let textField = searchBar.value(forKey: "_searchField") as! UITextField
|
||||
textField.font = UIFont(name: textField.font!.fontName, size: 15.0)
|
||||
|
||||
hideHeader()
|
||||
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11, *) {
|
||||
} else {
|
||||
// Exmaple: Add rounding corners on iOS 10
|
||||
visualEffectView.layer.cornerRadius = 9.0
|
||||
visualEffectView.clipsToBounds = true
|
||||
|
||||
// Exmaple: Add shadow manually on iOS 10
|
||||
view.layer.insertSublayer(shadowLayer, at: 0)
|
||||
let rect = visualEffectView.frame
|
||||
let path = UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.topLeft, .topRight],
|
||||
cornerRadii: CGSize(width: 9.0, height: 9.0))
|
||||
shadowLayer.frame = visualEffectView.frame
|
||||
shadowLayer.shadowPath = path.cgPath
|
||||
shadowLayer.shadowColor = UIColor.black.cgColor
|
||||
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0)
|
||||
shadowLayer.shadowOpacity = 0.2
|
||||
shadowLayer.shadowRadius = 3.0
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
if let cell = cell as? SearchCell {
|
||||
switch indexPath.row {
|
||||
case 0:
|
||||
cell.iconImageView.image = UIImage(named: "mark")
|
||||
cell.titleLabel.text = "Marked Location"
|
||||
cell.subTitleLabel.text = "Golden Gate Bridge, San Francisco"
|
||||
case 1:
|
||||
cell.iconImageView.image = UIImage(named: "like")
|
||||
cell.titleLabel.text = "Favorites"
|
||||
cell.subTitleLabel.text = "0 Places"
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: false)
|
||||
}
|
||||
|
||||
func showHeader() {
|
||||
changeHeader(height: 116.0)
|
||||
}
|
||||
|
||||
func hideHeader() {
|
||||
changeHeader(height: 0.0)
|
||||
}
|
||||
|
||||
func changeHeader(height: CGFloat) {
|
||||
tableView.beginUpdates()
|
||||
if let headerView = tableView.tableHeaderView {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
var frame = headerView.frame
|
||||
frame.size.height = height
|
||||
self.tableView.tableHeaderView?.frame = frame
|
||||
}
|
||||
}
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
public class SearchPanelLandscapeLayout: FloatingPanelLayout {
|
||||
public var initialPosition: FloatingPanelPosition {
|
||||
return .tip
|
||||
}
|
||||
|
||||
public var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return [.full, .tip]
|
||||
}
|
||||
|
||||
public func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return 16.0
|
||||
case .tip: return 69.0
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
if #available(iOS 11.0, *) {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
class SearchCell: UITableViewCell {
|
||||
@IBOutlet weak var iconImageView: UIImageView!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var subTitleLabel: UILabel!
|
||||
}
|
||||
|
||||
class SearchHeaderView: UIView {
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
@@ -7,33 +7,17 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5442E22425FC51AF00A26F43 /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22325FC51AF00A26F43 /* ImageViewController.swift */; };
|
||||
5442E22825FC51E200A26F43 /* MultiPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22725FC51E200A26F43 /* MultiPanelController.swift */; };
|
||||
5442E22C25FC521F00A26F43 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22B25FC521F00A26F43 /* SettingsViewController.swift */; };
|
||||
5442E23025FC525200A26F43 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22F25FC525200A26F43 /* TabBarViewController.swift */; };
|
||||
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E23325FC528400A26F43 /* DetailViewController.swift */; };
|
||||
5442E23A25FC52CD00A26F43 /* ModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E23925FC52CD00A26F43 /* ModalViewController.swift */; };
|
||||
5442E24025FC533800A26F43 /* DebugTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E23F25FC533800A26F43 /* DebugTableViewController.swift */; };
|
||||
5442E24425FC538200A26F43 /* InspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E24325FC538200A26F43 /* InspectorViewController.swift */; };
|
||||
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E24925FC53C100A26F43 /* DebugTextViewController.swift */; };
|
||||
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E25125FC541700A26F43 /* NestedScrollViewController.swift */; };
|
||||
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54496C58263A7E5A0031E0C8 /* UseCaseController.swift */; };
|
||||
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9ED21511E6300CA77B8 /* AppDelegate.swift */; };
|
||||
545DB9F021511E6300CA77B8 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9EF21511E6300CA77B8 /* MainViewController.swift */; };
|
||||
545DB9F021511E6300CA77B8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9EF21511E6300CA77B8 /* ViewController.swift */; };
|
||||
545DB9F321511E6300CA77B8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F121511E6300CA77B8 /* Main.storyboard */; };
|
||||
545DB9F521511E6400CA77B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F421511E6400CA77B8 /* Assets.xcassets */; };
|
||||
545DB9F821511E6400CA77B8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F621511E6400CA77B8 /* LaunchScreen.storyboard */; };
|
||||
545DBA0321511E6400CA77B8 /* SampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0221511E6400CA77B8 /* SampleTests.swift */; };
|
||||
545DBA0E21511E6400CA77B8 /* SampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0D21511E6400CA77B8 /* SampleUITests.swift */; };
|
||||
546341A125C6415100CA0596 /* UseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341A025C6415100CA0596 /* UseCase.swift */; };
|
||||
546341AC25C6426500CA0596 /* CustomState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341AB25C6426500CA0596 /* CustomState.swift */; };
|
||||
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; };
|
||||
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* Extensions.swift */; };
|
||||
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */; };
|
||||
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD35A263A75EB006A36EA /* PanelLayouts.swift */; };
|
||||
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD364263A765F006A36EA /* PagePanelController.swift */; };
|
||||
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
54B51116216AFE5F0033A6F3 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* UIExtensions.swift */; };
|
||||
54B5113C216C40670033A6F3 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113B216C40670033A6F3 /* FloatingPanel.framework */; };
|
||||
54B5113D216C40670033A6F3 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113B216C40670033A6F3 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* UIComponents.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -54,13 +38,13 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
549D23CD233C7779008EF4D7 /* Embed Frameworks */ = {
|
||||
54B5111C216C3B300033A6F3 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
|
||||
54B5113D216C40670033A6F3 /* FloatingPanel.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -68,20 +52,9 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5442E22325FC51AF00A26F43 /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
||||
5442E22725FC51E200A26F43 /* MultiPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPanelController.swift; sourceTree = "<group>"; };
|
||||
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
5442E22F25FC525200A26F43 /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = "<group>"; };
|
||||
5442E23325FC528400A26F43 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
|
||||
5442E23925FC52CD00A26F43 /* ModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalViewController.swift; sourceTree = "<group>"; };
|
||||
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTableViewController.swift; sourceTree = "<group>"; };
|
||||
5442E24325FC538200A26F43 /* InspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorViewController.swift; sourceTree = "<group>"; };
|
||||
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTextViewController.swift; sourceTree = "<group>"; };
|
||||
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedScrollViewController.swift; sourceTree = "<group>"; };
|
||||
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCaseController.swift; sourceTree = "<group>"; };
|
||||
545DB9EA21511E6300CA77B8 /* Samples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Samples.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
545DB9EF21511E6300CA77B8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
||||
545DB9EF21511E6300CA77B8 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
545DB9F221511E6300CA77B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
545DB9F421511E6400CA77B8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
545DB9F721511E6400CA77B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
@@ -92,14 +65,9 @@
|
||||
545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DBA0D21511E6400CA77B8 /* SampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleUITests.swift; sourceTree = "<group>"; };
|
||||
545DBA0F21511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
546341A025C6415100CA0596 /* UseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCase.swift; sourceTree = "<group>"; };
|
||||
546341AB25C6426500CA0596 /* CustomState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomState.swift; sourceTree = "<group>"; };
|
||||
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54B51115216AFE5F0033A6F3 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||
54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupplementaryViews.swift; sourceTree = "<group>"; };
|
||||
54EAD35A263A75EB006A36EA /* PanelLayouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelLayouts.swift; sourceTree = "<group>"; };
|
||||
54EAD364263A765F006A36EA /* PagePanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagePanelController.swift; sourceTree = "<group>"; };
|
||||
5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
|
||||
54B51115216AFE5F0033A6F3 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
|
||||
54B5113B216C40670033A6F3 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54CDC5D7215BBE23007D205C /* UIComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIComponents.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -107,8 +75,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */,
|
||||
54B5113C216C40670033A6F3 /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -129,32 +96,15 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
5442E22225FC519700A26F43 /* ContentViewControllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */,
|
||||
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */,
|
||||
5442E23325FC528400A26F43 /* DetailViewController.swift */,
|
||||
5442E24325FC538200A26F43 /* InspectorViewController.swift */,
|
||||
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
|
||||
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */,
|
||||
5442E23925FC52CD00A26F43 /* ModalViewController.swift */,
|
||||
5442E22725FC51E200A26F43 /* MultiPanelController.swift */,
|
||||
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */,
|
||||
5442E22F25FC525200A26F43 /* TabBarViewController.swift */,
|
||||
);
|
||||
path = ContentViewControllers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
545DB9E121511E6300CA77B8 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */,
|
||||
54B5113B216C40670033A6F3 /* FloatingPanel.framework */,
|
||||
545DB9EC21511E6300CA77B8 /* Sources */,
|
||||
545DBA0121511E6400CA77B8 /* Tests */,
|
||||
545DBA0C21511E6400CA77B8 /* UITests */,
|
||||
545DB9EB21511E6300CA77B8 /* Products */,
|
||||
5D82A6AB28D18438006A44BA /* Frameworks */,
|
||||
545DBA1B2151CC1000CA77B8 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -175,13 +125,9 @@
|
||||
545DB9F621511E6400CA77B8 /* LaunchScreen.storyboard */,
|
||||
545DB9F121511E6300CA77B8 /* Main.storyboard */,
|
||||
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */,
|
||||
545DB9EF21511E6300CA77B8 /* MainViewController.swift */,
|
||||
546341AA25C6421000CA0596 /* UseCases */,
|
||||
5442E22225FC519700A26F43 /* ContentViewControllers */,
|
||||
54EAD35A263A75EB006A36EA /* PanelLayouts.swift */,
|
||||
546341AB25C6426500CA0596 /* CustomState.swift */,
|
||||
54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */,
|
||||
54B51115216AFE5F0033A6F3 /* Extensions.swift */,
|
||||
545DB9EF21511E6300CA77B8 /* ViewController.swift */,
|
||||
54B51115216AFE5F0033A6F3 /* UIExtensions.swift */,
|
||||
54CDC5D7215BBE23007D205C /* UIComponents.swift */,
|
||||
545DB9F921511E6400CA77B8 /* Info.plist */,
|
||||
);
|
||||
path = Sources;
|
||||
@@ -205,20 +151,9 @@
|
||||
path = UITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
546341AA25C6421000CA0596 /* UseCases */ = {
|
||||
545DBA1B2151CC1000CA77B8 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
546341A025C6415100CA0596 /* UseCase.swift */,
|
||||
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */,
|
||||
54EAD364263A765F006A36EA /* PagePanelController.swift */,
|
||||
);
|
||||
path = UseCases;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5D82A6AB28D18438006A44BA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -230,10 +165,11 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 545DBA1221511E6400CA77B8 /* Build configuration list for PBXNativeTarget "Samples" */;
|
||||
buildPhases = (
|
||||
54D7209621D4DB970054A255 /* ShellScript */,
|
||||
545DB9E621511E6300CA77B8 /* Sources */,
|
||||
545DB9E721511E6300CA77B8 /* Frameworks */,
|
||||
545DB9E821511E6300CA77B8 /* Resources */,
|
||||
549D23CD233C7779008EF4D7 /* Embed Frameworks */,
|
||||
54B5111C216C3B300033A6F3 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -287,7 +223,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1000;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = scenee;
|
||||
TargetAttributes = {
|
||||
545DB9E921511E6300CA77B8 = {
|
||||
@@ -350,30 +286,35 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
54D7209621D4DB970054A255 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $(git rev-parse --abbrev-ref HEAD)($(git rev-parse --short HEAD))\" $SRCROOT/$INFOPLIST_FILE\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
545DB9E621511E6300CA77B8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */,
|
||||
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */,
|
||||
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */,
|
||||
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
|
||||
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */,
|
||||
546341AC25C6426500CA0596 /* CustomState.swift in Sources */,
|
||||
5442E23A25FC52CD00A26F43 /* ModalViewController.swift in Sources */,
|
||||
5442E22425FC51AF00A26F43 /* ImageViewController.swift in Sources */,
|
||||
5442E24025FC533800A26F43 /* DebugTableViewController.swift in Sources */,
|
||||
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */,
|
||||
5442E22825FC51E200A26F43 /* MultiPanelController.swift in Sources */,
|
||||
546341A125C6415100CA0596 /* UseCase.swift in Sources */,
|
||||
545DB9F021511E6300CA77B8 /* MainViewController.swift in Sources */,
|
||||
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */,
|
||||
54B51116216AFE5F0033A6F3 /* UIExtensions.swift in Sources */,
|
||||
545DB9F021511E6300CA77B8 /* ViewController.swift in Sources */,
|
||||
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */,
|
||||
5442E23025FC525200A26F43 /* TabBarViewController.swift in Sources */,
|
||||
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */,
|
||||
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */,
|
||||
5442E24425FC538200A26F43 /* InspectorViewController.swift in Sources */,
|
||||
5442E22C25FC521F00A26F43 /* SettingsViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -454,7 +395,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -516,7 +456,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -558,11 +497,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -581,11 +516,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -27,6 +27,8 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@@ -36,8 +38,8 @@
|
||||
ReferencedContainer = "container:Samples.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -59,6 +61,8 @@
|
||||
ReferencedContainer = "container:Samples.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
FloatingPanelSurfaceView.appearance().shadowHidden = false
|
||||
FloatingPanelSurfaceView.appearance().cornerRadius = 6.0
|
||||
// FloatingPanelSurfaceView.appearance().backgroundColor = .lightGray
|
||||
// FloatingPanelBackdropView.appearance().backgroundColor = .red
|
||||
// GrabberHandleView.appearance().barColor = .red
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "IMG_0003.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 202 KiB |
@@ -1,21 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<device id="retina5_9" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="Cjh-iX-VQw">
|
||||
<objects>
|
||||
<navigationController storyboardIdentifier="RootNavigationController" id="RoN-h0-uBD" sceneMemberID="viewController">
|
||||
<navigationController id="RoN-h0-uBD" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="hNW-5m-Omi">
|
||||
<rect key="frame" x="0.0" y="48" width="414" height="96"/>
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
@@ -29,24 +29,24 @@
|
||||
<!--Samples-->
|
||||
<scene sceneID="35L-Gs-Vts">
|
||||
<objects>
|
||||
<viewController id="jF4-A0-Eq6" customClass="MainViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="jF4-A0-Eq6" customClass="SampleListViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Smh-Bd-AAc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7IS-PU-x0P">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="M0G-C8-hAO" style="IBUITableViewCellStyleDefault" id="ySY-oA-g81">
|
||||
<rect key="frame" x="0.0" y="50" width="414" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ySY-oA-g81" id="sXB-nH-2g2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="M0G-C8-hAO">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
|
||||
<rect key="frame" x="15" y="0.0" width="345" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
@@ -58,14 +58,14 @@
|
||||
</prototypes>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="39L-Nq-qfp"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="top" secondItem="Smh-Bd-AAc" secondAttribute="top" id="6yd-jv-ey3"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="leading" secondItem="39L-Nq-qfp" secondAttribute="leading" id="Z6Y-Dc-cei"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="Smh-Bd-AAc" secondAttribute="bottom" id="fNW-DP-lhV"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="39L-Nq-qfp" secondAttribute="bottom" id="fNW-DP-lhV"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="trailing" secondItem="39L-Nq-qfp" secondAttribute="trailing" id="vfY-Rc-FOI"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="39L-Nq-qfp"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Samples" id="wCF-su-7up">
|
||||
<barButtonItem key="rightBarButtonItem" title="Settings" id="rbH-U3-XyA">
|
||||
@@ -87,79 +87,69 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="SettingsViewController" id="C1X-9Z-TyQ" customClass="SettingsViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="af9-Zr-Ppc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="197.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="197.33000000000001"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="n93-ZL-fmC">
|
||||
<rect key="frame" x="32" y="16" width="311" height="149.5"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="n93-ZL-fmC">
|
||||
<rect key="frame" x="32" y="16" width="311" height="147.33333333333334"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="DCi-Iv-o6d">
|
||||
<rect key="frame" x="0.0" y="0.0" width="311" height="53"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version: 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WmC-Tq-NDN">
|
||||
<rect key="frame" x="118.33333333333334" y="0.0" width="74.333333333333343" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UINavigationBar" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ulg-gS-ah0">
|
||||
<rect key="frame" x="90.666666666666686" y="33" width="130" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="126" translatesAutoresizingMaskIntoConstraints="NO" id="uEf-g4-CeU">
|
||||
<rect key="frame" x="23.333333333333343" y="69.333333333333329" width="264.33333333333326" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version: 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WmC-Tq-NDN">
|
||||
<rect key="frame" x="118.5" y="0.0" width="74.5" height="17.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UINavigationBar" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ulg-gS-ah0">
|
||||
<rect key="frame" x="78.5" y="25.5" width="154.5" height="27.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Large Titles" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogl-S5-4tJ">
|
||||
<rect key="frame" x="0.0" y="5.3333333333333428" width="89.333333333333329" height="20.333333333333332"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="js8-Qv-lUC">
|
||||
<rect key="frame" x="215.33333333333334" y="0.0" width="51.000000000000028" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleLargeTitle:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="FJS-Ty-mCY"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="J8j-7w-yCZ">
|
||||
<rect key="frame" x="0.0" y="69" width="311" height="80.5"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="126" translatesAutoresizingMaskIntoConstraints="NO" id="ZtZ-Dz-4cC">
|
||||
<rect key="frame" x="23.333333333333343" y="116.33333333333334" width="264.66666666666663" height="31"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="uEf-g4-CeU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="311" height="32"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Large Titles" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogl-S5-4tJ">
|
||||
<rect key="frame" x="0.0" y="5.5" width="254" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="js8-Qv-lUC">
|
||||
<rect key="frame" x="262" y="0.5" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleLargeTitle:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="FJS-Ty-mCY"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="ZtZ-Dz-4cC">
|
||||
<rect key="frame" x="0.0" y="48" width="311" height="32.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Translucent" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Z5i-rm-QgL">
|
||||
<rect key="frame" x="0.0" y="6" width="254" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="s6b-j9-8Kw">
|
||||
<rect key="frame" x="262" y="0.5" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleTranslucent:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="nL4-3L-9hh"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Translucent" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Z5i-rm-QgL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="89.666666666666671" height="31"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="s6b-j9-8Kw">
|
||||
<rect key="frame" x="215.66666666666666" y="0.0" width="50.999999999999972" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleTranslucent:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="nL4-3L-9hh"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="0hr-ty-yWm"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="n93-ZL-fmC" secondAttribute="bottom" constant="32" id="2Ey-ou-E1M"/>
|
||||
<constraint firstItem="0hr-ty-yWm" firstAttribute="bottom" secondItem="n93-ZL-fmC" secondAttribute="bottom" id="2Ey-ou-E1M"/>
|
||||
<constraint firstAttribute="trailing" secondItem="n93-ZL-fmC" secondAttribute="trailing" constant="32" id="DdZ-eB-F5s"/>
|
||||
<constraint firstItem="n93-ZL-fmC" firstAttribute="leading" secondItem="af9-Zr-Ppc" secondAttribute="leading" constant="32" id="TyK-GP-Ari"/>
|
||||
<constraint firstItem="n93-ZL-fmC" firstAttribute="top" secondItem="af9-Zr-Ppc" secondAttribute="topMargin" constant="16" id="mbC-6H-z9M"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="0hr-ty-yWm"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="197.33000000000001"/>
|
||||
<connections>
|
||||
@@ -177,11 +167,11 @@
|
||||
<objects>
|
||||
<viewController id="RpE-lI-27a" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="JER-jz-KSq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
|
||||
<rect key="frame" x="20" y="48" width="39" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="RpE-lI-27a" eventType="touchUpInside" id="hj3-Xv-6Gq"/>
|
||||
@@ -189,13 +179,13 @@
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="954-Dk-zvc"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="954-Dk-zvc" secondAttribute="top" id="18k-sV-PgT"/>
|
||||
<constraint firstItem="954-Dk-zvc" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="IvG-yp-yzI" secondAttribute="trailing" id="mpr-u5-MZu"/>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="954-Dk-zvc" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="954-Dk-zvc"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" tag="1" title="Layout 2" id="qb3-RB-B28"/>
|
||||
</viewController>
|
||||
@@ -208,11 +198,11 @@
|
||||
<objects>
|
||||
<viewController id="pOk-Zm-vD9" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="85d-ub-G8k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NbG-e8-HdI">
|
||||
<rect key="frame" x="20" y="48" width="39" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NbG-e8-HdI">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="pOk-Zm-vD9" eventType="touchUpInside" id="111-PD-Pop"/>
|
||||
@@ -220,13 +210,13 @@
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="0ao-SI-QZW"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="0ao-SI-QZW" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="NbG-e8-HdI" secondAttribute="trailing" id="K9F-6x-KWn"/>
|
||||
<constraint firstItem="NbG-e8-HdI" firstAttribute="top" secondItem="0ao-SI-QZW" secondAttribute="top" id="nsE-so-rTl"/>
|
||||
<constraint firstItem="NbG-e8-HdI" firstAttribute="leading" secondItem="0ao-SI-QZW" secondAttribute="leading" constant="20" id="sF4-Dm-aoY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="0ao-SI-QZW"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" tag="2" title="Layout 3" id="RJD-TF-Sdh"/>
|
||||
</viewController>
|
||||
@@ -239,11 +229,11 @@
|
||||
<objects>
|
||||
<viewController id="lto-Zc-Vtp" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="ji9-Ez-N7i">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
|
||||
<rect key="frame" x="20" y="48" width="39" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="YL4-GP-ZEZ"/>
|
||||
@@ -251,13 +241,13 @@
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="5Ns-4l-Ufg"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="5Ns-4l-Ufg" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
|
||||
<constraint firstItem="5Ns-4l-Ufg" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="eFN-tN-4Ct" secondAttribute="trailing" id="OzZ-Dz-RNF"/>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="5Ns-4l-Ufg" secondAttribute="top" id="hUV-3a-XkY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="5Ns-4l-Ufg"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" title="Layout 1" id="HEV-kf-jxH"/>
|
||||
</viewController>
|
||||
@@ -274,19 +264,20 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" text="Change this text" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ge4-RW-Gmz">
|
||||
<rect key="frame" x="125.5" y="24" width="124" height="20.5"/>
|
||||
<rect key="frame" x="24" y="24" width="327" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="ouu-g9-OiX"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="ge4-RW-Gmz" secondAttribute="trailing" constant="24" id="V59-MD-Lcg"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="leading" secondItem="eLM-xc-d9e" secondAttribute="leading" constant="24" id="hAO-P0-7Kw"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="top" secondItem="eLM-xc-d9e" secondAttribute="top" constant="24" id="j0s-fd-MYj"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ge4-RW-Gmz" secondAttribute="bottom" constant="24" id="tEn-PO-nVD"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="centerX" secondItem="eLM-xc-d9e" secondAttribute="centerX" id="vh3-l7-uY8"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="ouu-g9-OiX"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
</viewController>
|
||||
@@ -294,71 +285,6 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2753" y="734"/>
|
||||
</scene>
|
||||
<!--Image View Controller-->
|
||||
<scene sceneID="NAI-Rh-ZQ6">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ImageViewController" id="VWY-cF-RoY" customClass="ImageViewController" customModule="Samples" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="dAf-gD-ghB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Gs4-S6-Goh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="t7x-eG-MKh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="49"/>
|
||||
<color key="backgroundColor" systemColor="systemOrangeColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="49" id="DmG-pt-gij"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" alwaysBounceHorizontal="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kRA-qy-GpJ">
|
||||
<rect key="frame" x="0.0" y="49" width="375" height="680"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" image="IMG_0003" translatesAutoresizingMaskIntoConstraints="NO" id="rGf-jW-WNf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="750" height="501"/>
|
||||
<color key="backgroundColor" systemColor="systemPurpleColor"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="rGf-jW-WNf" secondAttribute="trailing" id="472-Be-cUl"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rGf-jW-WNf" secondAttribute="bottom" id="ncs-tN-3Wx"/>
|
||||
<constraint firstItem="rGf-jW-WNf" firstAttribute="leading" secondItem="kRA-qy-GpJ" secondAttribute="leading" id="rlv-5Y-utR"/>
|
||||
<constraint firstItem="rGf-jW-WNf" firstAttribute="top" secondItem="kRA-qy-GpJ" secondAttribute="top" id="zum-Zl-Wzz"/>
|
||||
</constraints>
|
||||
</scrollView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SEa-7Y-wa9">
|
||||
<rect key="frame" x="0.0" y="729" width="375" height="49"/>
|
||||
<color key="backgroundColor" systemColor="systemTealColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="49" id="3mS-zi-8BP"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="hCg-v5-nJs"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="Gs4-S6-Goh" secondAttribute="trailing" id="6z7-Md-pxr"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Gs4-S6-Goh" secondAttribute="bottom" id="PcQ-bu-yT3"/>
|
||||
<constraint firstItem="Gs4-S6-Goh" firstAttribute="top" secondItem="dAf-gD-ghB" secondAttribute="top" id="zGx-Wd-hjz"/>
|
||||
<constraint firstItem="Gs4-S6-Goh" firstAttribute="leading" secondItem="dAf-gD-ghB" secondAttribute="leading" id="zxi-Ty-U7L"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="footerView" destination="SEa-7Y-wa9" id="Gzj-dP-YXl"/>
|
||||
<outlet property="headerView" destination="t7x-eG-MKh" id="njM-un-U8q"/>
|
||||
<outlet property="scrollView" destination="kRA-qy-GpJ" id="iWC-o4-APi"/>
|
||||
<outlet property="stackView" destination="Gs4-S6-Goh" id="f1D-bO-mjr"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="pnR-69-Ek4" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3388" y="734.48275862068965"/>
|
||||
</scene>
|
||||
<!--Tab Bar View Controller-->
|
||||
<scene sceneID="nQ5-PV-qFw">
|
||||
<objects>
|
||||
@@ -383,53 +309,46 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="720"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vut-mK-Y4t" customClass="SafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="720" width="375" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="744" width="375" height="34"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
|
||||
<rect key="frame" x="20" y="48" width="39" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="MSC-ch-YJK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="44" translatesAutoresizingMaskIntoConstraints="NO" id="9p4-06-y2T">
|
||||
<rect key="frame" x="134.5" y="136" width="106" height="326"/>
|
||||
<rect key="frame" x="139.66666666666666" y="132" width="96" height="252"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="30"/>
|
||||
<state key="normal" title="Move to full"/>
|
||||
<connections>
|
||||
<action selector="moveToFullWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="TDe-3J-gIR"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2u5-cH-RAN">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2u5-cH-RAN">
|
||||
<rect key="frame" x="0.0" y="74" width="85" height="30"/>
|
||||
<state key="normal" title="Move to half"/>
|
||||
<connections>
|
||||
<action selector="moveToHalfWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="12s-o7-Et5"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M4A-iO-RIE">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M4A-iO-RIE">
|
||||
<rect key="frame" x="0.0" y="148" width="77" height="30"/>
|
||||
<state key="normal" title="Move to tip"/>
|
||||
<connections>
|
||||
<action selector="moveToTipWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="BmL-91-9ai"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="swr-XM-GzZ">
|
||||
<rect key="frame" x="0.0" y="222" width="106" height="30"/>
|
||||
<state key="normal" title="Move to hidden"/>
|
||||
<connections>
|
||||
<action selector="moveToHiddenWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="jfJ-0f-fdk"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="szf-HE-QTk">
|
||||
<rect key="frame" x="0.0" y="296" width="96" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="szf-HE-QTk">
|
||||
<rect key="frame" x="0.0" y="222" width="96" height="30"/>
|
||||
<state key="normal" title="Update layout"/>
|
||||
<connections>
|
||||
<action selector="updateLayout:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="Woz-a7-YMJ"/>
|
||||
@@ -438,7 +357,6 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="kjr-TP-fcM"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="top" id="3VR-hj-zeQ"/>
|
||||
@@ -450,6 +368,7 @@
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="centerX" secondItem="kjr-TP-fcM" secondAttribute="centerX" id="l8t-p3-ETf"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="bottom" id="rMy-JT-t4B"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="kjr-TP-fcM"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
@@ -542,7 +461,6 @@
|
||||
</constraints>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="ufS-Rf-F2F"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
@@ -551,6 +469,7 @@
|
||||
<constraint firstAttribute="bottom" secondItem="sBe-tN-uMi" secondAttribute="bottom" id="jzB-47-P7e"/>
|
||||
<constraint firstItem="ufS-Rf-F2F" firstAttribute="trailing" secondItem="sBe-tN-uMi" secondAttribute="trailing" id="nHG-wg-pLP"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="ufS-Rf-F2F"/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="tOa-bf-zGz" appends="YES" id="zle-Sz-M3U"/>
|
||||
<outletCollection property="gestureRecognizers" destination="SCk-hG-weZ" appends="YES" id="OcK-FK-Lac"/>
|
||||
@@ -598,11 +517,11 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="48" width="375" height="730"/>
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="700"/>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="319" y="48" width="44" height="44"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="319" y="44" width="44" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="0jg-5D-A1F"/>
|
||||
<constraint firstAttribute="width" constant="44" id="1Cq-PA-wgW"/>
|
||||
@@ -611,34 +530,17 @@
|
||||
<action selector="closeWithSender:" destination="YC8-ae-15L" eventType="touchUpInside" id="Z2v-19-S5k"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="qux-uG-4o2">
|
||||
<rect key="frame" x="8" y="56" width="148" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="fitToBounds" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7lq-d3-PKi">
|
||||
<rect key="frame" x="0.0" y="5.5" width="91" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0MA-lV-KjS">
|
||||
<rect key="frame" x="99" y="0.0" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="modeChanged:" destination="YC8-ae-15L" eventType="valueChanged" id="IQ8-u2-Rib"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="tP3-oJ-4EB">
|
||||
<rect key="frame" x="130.5" y="88" width="114" height="134"/>
|
||||
<rect key="frame" x="130.66666666666666" y="132" width="114" height="134"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c5r-jU-haj">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c5r-jU-haj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="114" height="30"/>
|
||||
<state key="normal" title="Show"/>
|
||||
<connections>
|
||||
<action selector="buttonPressed:" destination="YC8-ae-15L" eventType="touchUpInside" id="Mi1-o6-TWt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wmd-ab-Nz3">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wmd-ab-Nz3">
|
||||
<rect key="frame" x="0.0" y="52" width="114" height="30"/>
|
||||
<state key="normal" title="Present Modallly"/>
|
||||
<connections>
|
||||
@@ -646,7 +548,7 @@
|
||||
<segue destination="bYI-y3-Rzb" kind="presentation" identifier="PresentModallySegue" id="3yq-HE-Tgn"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="01L-lp-oy6">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="01L-lp-oy6">
|
||||
<rect key="frame" x="0.0" y="104" width="114" height="30"/>
|
||||
<state key="normal" title="Update Layout"/>
|
||||
<connections>
|
||||
@@ -656,25 +558,23 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="aOK-7l-cA6"/>
|
||||
<color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" priority="750" id="EQy-cr-F2Y"/>
|
||||
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="EQy-cr-F2Y"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="centerX" secondItem="aOK-7l-cA6" secondAttribute="centerX" id="EsD-Vf-dNZ"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="Fff-HL-4mo"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="bottom" secondItem="g7l-kO-y7q" secondAttribute="bottom" priority="750" id="JOL-wC-w74"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="bottom" secondItem="g7l-kO-y7q" secondAttribute="bottom" id="JOL-wC-w74"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="RiJ-Hb-OOZ"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="Sof-yL-mwK"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="g7l-kO-y7q" secondAttribute="top" priority="750" constant="88" id="Zhb-Ss-epe"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" constant="88" id="Zhb-Ss-epe"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="kkp-Yo-FQW"/>
|
||||
<constraint firstItem="aOK-7l-cA6" firstAttribute="trailing" secondItem="noi-1a-5bZ" secondAttribute="trailing" constant="12" id="lv9-Nf-HNB"/>
|
||||
<constraint firstItem="qux-uG-4o2" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" constant="8" id="naa-cf-ZIc"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="oVC-i1-TwS"/>
|
||||
<constraint firstItem="aOK-7l-cA6" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" priority="750" id="rW2-mF-5DR"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="top" relation="greaterThanOrEqual" secondItem="tP3-oJ-4EB" secondAttribute="bottom" priority="750" constant="88" id="vKQ-h9-uKt"/>
|
||||
<constraint firstItem="qux-uG-4o2" firstAttribute="leading" secondItem="g7l-kO-y7q" secondAttribute="leading" constant="8" id="zXb-R9-bMO"/>
|
||||
<constraint firstItem="aOK-7l-cA6" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" id="rW2-mF-5DR"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="top" relation="greaterThanOrEqual" secondItem="tP3-oJ-4EB" secondAttribute="bottom" constant="88" id="vKQ-h9-uKt"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="aOK-7l-cA6"/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="6Ca-p8-7uF" appends="YES" id="xOy-f1-NZE"/>
|
||||
<outletCollection property="gestureRecognizers" destination="SPY-Vr-XDT" appends="YES" id="vgS-Am-jhQ"/>
|
||||
@@ -684,8 +584,6 @@
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="closeButton" destination="noi-1a-5bZ" id="eWQ-ha-8y7"/>
|
||||
<outlet property="intrinsicHeightConstraint" destination="vKQ-h9-uKt" id="QpA-WD-b17"/>
|
||||
<outlet property="modeChangeView" destination="qux-uG-4o2" id="1Nq-fE-dXw"/>
|
||||
<segue destination="bYI-y3-Rzb" kind="show" identifier="ShowSegue" id="r1P-2i-NDe"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
@@ -706,7 +604,7 @@
|
||||
</connections>
|
||||
</pongPressGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="653.60000000000002" y="733.74384236453204"/>
|
||||
<point key="canvasLocation" x="655" y="734"/>
|
||||
</scene>
|
||||
<!--Debug Text View Controller-->
|
||||
<scene sceneID="Bkq-O7-q4A">
|
||||
@@ -762,7 +660,6 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
@@ -770,6 +667,7 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="17" id="fiO-LL-nSC"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
@@ -783,27 +681,7 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
<point key="canvasLocation" x="-1" y="734"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<designables>
|
||||
<designable name="noi-1a-5bZ">
|
||||
<size key="intrinsicContentSize" width="30" height="30"/>
|
||||
</designable>
|
||||
</designables>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="r1P-2i-NDe"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
<image name="IMG_0003" width="750" height="501"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemOrangeColor">
|
||||
<color red="1" green="0.58431372549019611" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemPurpleColor">
|
||||
<color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemTealColor">
|
||||
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
class DebugTableViewController: InspectableViewController {
|
||||
// MARK: - Views
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
|
||||
return tableView
|
||||
}()
|
||||
lazy var buttonStackView: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.distribution = .fillEqually
|
||||
stackView.alignment = .trailing
|
||||
stackView.spacing = 10.0
|
||||
return stackView
|
||||
}()
|
||||
private lazy var reorderButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setTitle(Menu.reorder.rawValue, for: .normal)
|
||||
button.setTitleColor(view.tintColor, for: .normal)
|
||||
button.addTarget(self, action: #selector(reorderItems), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
private lazy var trackingSwitchWrapper: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .horizontal
|
||||
stackView.distribution = .fillProportionally
|
||||
stackView.alignment = .center
|
||||
stackView.spacing = 8.0
|
||||
stackView.addArrangedSubview(trackingLabel)
|
||||
stackView.addArrangedSubview(trackingSwitch)
|
||||
return stackView
|
||||
}()
|
||||
private lazy var trackingLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "Tracking"
|
||||
label.font = UIFont.systemFont(ofSize: 17.0, weight: .regular)
|
||||
return label
|
||||
}()
|
||||
private lazy var trackingSwitch: UISwitch = {
|
||||
let trackingSwitch = UISwitch()
|
||||
trackingSwitch.isOn = true
|
||||
trackingSwitch.addTarget(self, action: #selector(turnTrackingOn), for: .touchUpInside)
|
||||
return trackingSwitch
|
||||
}()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var kvoObservers: [NSKeyValueObservation] = []
|
||||
private lazy var items: [String] = {
|
||||
let items = (0..<100).map { "Items \($0)" }
|
||||
return Command.replace(items: items)
|
||||
}()
|
||||
private var itemHeight: CGFloat = 66.0
|
||||
|
||||
enum Menu: String, CaseIterable {
|
||||
case turnOffTracking = "Tracking"
|
||||
case reorder = "Reorder"
|
||||
}
|
||||
|
||||
enum Command: Int, CaseIterable {
|
||||
case animateScroll
|
||||
case changeContentSize
|
||||
case moveToFull
|
||||
case moveToHalf
|
||||
var text: String {
|
||||
switch self {
|
||||
case .animateScroll: return "Scroll in the middle"
|
||||
case .changeContentSize: return "Change content size"
|
||||
case .moveToFull: return "Move to Full"
|
||||
case.moveToHalf: return "Move to Half"
|
||||
}
|
||||
}
|
||||
|
||||
static func replace(items: [String]) -> [String] {
|
||||
return items.enumerated().map { (index, text) -> String in
|
||||
if let action = Command(rawValue: index) {
|
||||
return "\(index). \(action.text)"
|
||||
}
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
func execute(for vc: DebugTableViewController, sourceView: UIView) {
|
||||
switch self {
|
||||
case .animateScroll:
|
||||
vc.animateScroll()
|
||||
case .changeContentSize:
|
||||
vc.changeContentSize(sourceView: sourceView)
|
||||
case .moveToFull:
|
||||
vc.moveToFull()
|
||||
case .moveToHalf:
|
||||
vc.moveToHalf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
layoutTableView()
|
||||
layoutMenuStackView()
|
||||
setUpMenu()
|
||||
}
|
||||
|
||||
private func layoutTableView() {
|
||||
view.addSubview(tableView)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
|
||||
tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func layoutMenuStackView() {
|
||||
view.addSubview(buttonStackView)
|
||||
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
buttonStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 22.0),
|
||||
buttonStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -22.0),
|
||||
])
|
||||
}
|
||||
|
||||
private func setUpMenu() {
|
||||
for menu in Menu.allCases {
|
||||
switch menu {
|
||||
case .reorder:
|
||||
buttonStackView.addArrangedSubview(reorderButton)
|
||||
case .turnOffTracking:
|
||||
buttonStackView.addArrangedSubview(trackingSwitchWrapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Menu
|
||||
@objc
|
||||
private func reorderItems() {
|
||||
if reorderButton.titleLabel?.text == Menu.reorder.rawValue {
|
||||
tableView.isEditing = true
|
||||
reorderButton.setTitle("Cancel", for: .normal)
|
||||
} else {
|
||||
tableView.isEditing = false
|
||||
reorderButton.setTitle(Menu.reorder.rawValue, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
private func turnTrackingOn(_ sender: UISwitch) {
|
||||
guard let fpc = self.parent as? FloatingPanelController else { return }
|
||||
if sender.isOn {
|
||||
fpc.track(scrollView: tableView)
|
||||
} else {
|
||||
fpc.untrack(scrollView: tableView)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
private func execute(command: Command, sourceView: UIView) {
|
||||
command.execute(for: self, sourceView: sourceView)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func animateScroll() {
|
||||
tableView.scrollToRow(at: IndexPath(row: lround(Double(items.count) / 2.0),
|
||||
section: 0),
|
||||
at: .top, animated: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func changeContentSize(sourceView: UIView) {
|
||||
let actionSheet = UIAlertController(title: "Change content size", message: "", preferredStyle: .actionSheet)
|
||||
actionSheet.addAction(UIAlertAction(title: "Large", style: .default, handler: { (_) in
|
||||
self.itemHeight = 66.0
|
||||
self.changeItems(100)
|
||||
}))
|
||||
actionSheet.addAction(UIAlertAction(title: "Match", style: .default, handler: { (_) in
|
||||
switch self.tableView.bounds.height {
|
||||
case 585: // iPhone 6,7,8
|
||||
self.itemHeight = self.tableView.bounds.height / 13.0
|
||||
self.changeItems(13)
|
||||
case 656: // iPhone {6,7,8} Plus
|
||||
self.itemHeight = self.tableView.bounds.height / 16.0
|
||||
self.changeItems(16)
|
||||
default: // iPhone X family
|
||||
self.itemHeight = self.tableView.bounds.height / 12.0
|
||||
self.changeItems(12)
|
||||
}
|
||||
}))
|
||||
actionSheet.addAction(UIAlertAction(title: "Short", style: .default, handler: { (_) in
|
||||
self.itemHeight = 66.0
|
||||
self.changeItems(3)
|
||||
}))
|
||||
|
||||
if let popoverController = actionSheet.popoverPresentationController {
|
||||
popoverController.sourceView = sourceView
|
||||
popoverController.sourceRect = sourceView.bounds
|
||||
}
|
||||
|
||||
self.present(actionSheet, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func changeItems(_ count: Int) {
|
||||
items = Command.replace(items: (0..<count).map{ "\($0). No action" })
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
@objc
|
||||
private func moveToFull() {
|
||||
(self.parent as! FloatingPanelController).move(to: .full, animated: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func moveToHalf() {
|
||||
(self.parent as! FloatingPanelController).move(to: .half, animated: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func close(sender: UIButton) {
|
||||
// Remove FloatingPanel from a view
|
||||
(self.parent as! FloatingPanelController).removePanelFromParent(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension DebugTableViewController: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return items.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
return itemHeight
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
cell.textLabel?.text = items[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension DebugTableViewController: UITableViewDelegate {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
print("TableView --- ", scrollView.contentOffset, scrollView.contentInset)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
print("DebugTableViewController -- select row \(indexPath.row)")
|
||||
guard let action = Command(rawValue: indexPath.row) else { return }
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
execute(command: action, sourceView: cell ?? tableView)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
||||
return [
|
||||
UITableViewRowAction(style: .destructive, title: "Delete", handler: { (action, path) in
|
||||
self.items.remove(at: path.row)
|
||||
tableView.deleteRows(at: [path], with: .automatic)
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
|
||||
items.insert(items.remove(at: sourceIndexPath.row), at: destinationIndexPath.row)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
|
||||
final class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
@IBOutlet weak var textView: UITextView!
|
||||
@IBOutlet weak var textViewTopConstraint: NSLayoutConstraint!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
textView.delegate = self
|
||||
print("viewDidLoad: TextView --- ", textView.contentOffset, textView.contentInset)
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
textView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
print("viewWillLayoutSubviews: TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
print("viewDidLayoutSubviews: TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
print("TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
print("TextView --- ", scrollView.contentOffset, scrollView.contentInset)
|
||||
if #available(iOS 11.0, *) {
|
||||
print("TextView --- ", scrollView.adjustedContentInset)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func close(sender: UIButton) {
|
||||
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class DetailViewController: InspectableViewController {
|
||||
@IBOutlet weak var modeChangeView: UIStackView!
|
||||
@IBOutlet weak var intrinsicHeightConstraint: NSLayoutConstraint!
|
||||
@IBOutlet weak var closeButton: UIButton!
|
||||
@IBAction func close(sender: UIButton) {
|
||||
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func buttonPressed(_ sender: UIButton) {
|
||||
switch sender.titleLabel?.text {
|
||||
case "Show":
|
||||
performSegue(withIdentifier: "ShowSegue", sender: self)
|
||||
case "Present Modally":
|
||||
performSegue(withIdentifier: "PresentModallySegue", sender: self)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
@IBAction func modeChanged(_ sender: Any) {
|
||||
guard let fpc = parent as? FloatingPanelController else { return }
|
||||
fpc.contentMode = (fpc.contentMode == .static) ? .fitToBounds : .static
|
||||
}
|
||||
|
||||
@IBAction func tapped(_ sender: Any) {
|
||||
print("Detail panel is tapped!")
|
||||
}
|
||||
@IBAction func swipped(_ sender: Any) {
|
||||
print("Detail panel is swipped!")
|
||||
}
|
||||
@IBAction func longPressed(_ sender: Any) {
|
||||
print("Detail panel is longPressed!")
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class ImageViewController: UIViewController {
|
||||
class PanelLayout: FloatingPanelLayout {
|
||||
weak var targetGuide: UILayoutGuide?
|
||||
init(targetGuide: UILayoutGuide?) {
|
||||
self.targetGuide = targetGuide
|
||||
}
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .full
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
if #available(iOS 11.0, *), let targetGuide = targetGuide {
|
||||
return [
|
||||
.full: FloatingPanelAdaptiveLayoutAnchor(absoluteOffset: 0,
|
||||
contentLayout: targetGuide,
|
||||
referenceGuide: .superview),
|
||||
.half: FloatingPanelAdaptiveLayoutAnchor(fractionalOffset: 0.5,
|
||||
contentLayout: targetGuide,
|
||||
referenceGuide: .superview)
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 500,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var headerView: UIView!
|
||||
@IBOutlet weak var footerView: UIView!
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
@IBOutlet weak var stackView: UIStackView!
|
||||
|
||||
enum Mode {
|
||||
case onlyImage
|
||||
case withHeaderFooter
|
||||
}
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
func layoutGuideFor(mode: Mode) -> UILayoutGuide {
|
||||
switch mode {
|
||||
case .onlyImage:
|
||||
self.headerView.isHidden = true
|
||||
self.footerView.isHidden = true
|
||||
return scrollView.contentLayoutGuide
|
||||
case .withHeaderFooter:
|
||||
self.headerView.isHidden = false
|
||||
self.footerView.isHidden = false
|
||||
let guide = UILayoutGuide()
|
||||
view.addLayoutGuide(guide)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
scrollView.heightAnchor.constraint(equalTo: scrollView.contentLayoutGuide.heightAnchor),
|
||||
|
||||
guide.topAnchor.constraint(equalTo: stackView.topAnchor),
|
||||
guide.leftAnchor.constraint(equalTo: stackView.leftAnchor),
|
||||
guide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor),
|
||||
guide.rightAnchor.constraint(equalTo: stackView.rightAnchor),
|
||||
])
|
||||
return guide
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
|
||||
class InspectableViewController: UIViewController {
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
print(">>> Content View: viewWillLayoutSubviews", layoutInsets)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
print(">>> Content View: viewDidLayoutSubviews", layoutInsets)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
print(">>> Content View: viewWillAppear", layoutInsets)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
print(">>> Content View: viewDidAppear", view.bounds, layoutInsets)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
print(">>> Content View: viewWillDisappear")
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
print(">>> Content View: viewDidDisappear")
|
||||
}
|
||||
|
||||
override func willMove(toParent parent: UIViewController?) {
|
||||
super.willMove(toParent: parent)
|
||||
print(">>> Content View: willMove(toParent: \(String(describing: parent))")
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
print(">>> Content View: didMove(toParent: \(String(describing: parent))")
|
||||
}
|
||||
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
print(">>> Content View: willTransition(to: \(newCollection), with: \(coordinator))", layoutInsets)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
var fpc: FloatingPanelController!
|
||||
var consoleVC: DebugTextViewController!
|
||||
|
||||
@IBOutlet weak var safeAreaView: UIView!
|
||||
|
||||
var isNewlayout: Bool = false
|
||||
|
||||
override func viewDidLoad() {
|
||||
// Initialize FloatingPanelController
|
||||
fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
fpc.surfaceView.appearance = appearance
|
||||
|
||||
// Set a content view controller and track the scroll view
|
||||
let consoleVC = storyboard?.instantiateViewController(withIdentifier: "ConsoleViewController") as! DebugTextViewController
|
||||
fpc.set(contentViewController: consoleVC)
|
||||
fpc.track(scrollView: consoleVC.textView)
|
||||
|
||||
self.consoleVC = consoleVC
|
||||
|
||||
// Add FloatingPanel to self.view
|
||||
fpc.addPanel(toParent: self, at: view.subviews.firstIndex(of: safeAreaView) ?? -1)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
// Remove FloatingPanel from a view
|
||||
fpc.removePanelFromParent(animated: false)
|
||||
}
|
||||
|
||||
@IBAction func close(sender: UIButton) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func moveToFull(sender: UIButton) {
|
||||
fpc.move(to: .full, animated: true)
|
||||
}
|
||||
@IBAction func moveToHalf(sender: UIButton) {
|
||||
fpc.move(to: .half, animated: true)
|
||||
}
|
||||
@IBAction func moveToTip(sender: UIButton) {
|
||||
fpc.move(to: .tip, animated: true)
|
||||
}
|
||||
@IBAction func moveToHidden(sender: UIButton) {
|
||||
fpc.move(to: .hidden, animated: true)
|
||||
}
|
||||
@IBAction func updateLayout(_ sender: Any) {
|
||||
isNewlayout = !isNewlayout
|
||||
UIView.animate(withDuration: 0.5) {
|
||||
self.fpc.invalidateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
return (isNewlayout) ? ModalSecondLayout() : FloatingPanelBottomLayout()
|
||||
}
|
||||
|
||||
class ModalSecondLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
import WebKit
|
||||
|
||||
final class MultiPanelController: FloatingPanelController, FloatingPanelControllerDelegate {
|
||||
|
||||
private final class FirstPanelContentViewController: UIViewController {
|
||||
|
||||
lazy var webView: WKWebView = WKWebView()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.addSubview(webView)
|
||||
webView.frame = view.bounds
|
||||
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
webView.load(URLRequest(url: URL(string: "https://www.apple.com")!))
|
||||
|
||||
let vc = MultiSecondPanelController()
|
||||
vc.setUpContent()
|
||||
vc.addPanel(toParent: self)
|
||||
}
|
||||
}
|
||||
|
||||
private final class MultiSecondPanelController: FloatingPanelController {
|
||||
|
||||
private final class SecondPanelContentViewController: DebugTableViewController {}
|
||||
|
||||
func setUpContent() {
|
||||
contentInsetAdjustmentBehavior = .never
|
||||
let vc = SecondPanelContentViewController()
|
||||
vc.loadViewIfNeeded()
|
||||
vc.title = "Second Panel"
|
||||
vc.buttonStackView.isHidden = true
|
||||
let navigationController = UINavigationController(rootViewController: vc)
|
||||
navigationController.navigationBar.barTintColor = .white
|
||||
navigationController.navigationBar.titleTextAttributes = [
|
||||
.foregroundColor: UIColor.black
|
||||
]
|
||||
set(contentViewController: navigationController)
|
||||
self.track(scrollView: vc.tableView)
|
||||
surfaceView.containerMargins = .init(top: 24.0, left: 0.0, bottom: layoutInsets.bottom, right: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
layout = FirstViewLayout()
|
||||
isRemovalInteractionEnabled = true
|
||||
|
||||
let vc = FirstPanelContentViewController()
|
||||
set(contentViewController: vc)
|
||||
track(scrollView: vc.webView.scrollView)
|
||||
}
|
||||
|
||||
private final class FirstViewLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .full
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 40.0, edge: .top, referenceGuide: .superview)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
|
||||
final class NestedScrollViewController: UIViewController {
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
@IBOutlet weak var nestedScrollView: UIScrollView!
|
||||
|
||||
@IBAction func longPressed(_ sender: Any) {
|
||||
print("LongPressed!")
|
||||
}
|
||||
@IBAction func swipped(_ sender: Any) {
|
||||
print("Swipped!")
|
||||
}
|
||||
@IBAction func tapped(_ sender: Any) {
|
||||
print("Tapped!")
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class SettingsViewController: InspectableViewController {
|
||||
@IBOutlet weak var largeTitlesSwicth: UISwitch!
|
||||
@IBOutlet weak var translucentSwicth: UISwitch!
|
||||
@IBOutlet weak var versionLabel: UILabel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
versionLabel.text = "Version: \(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "--")"
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {
|
||||
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
|
||||
largeTitlesSwicth.setOn(prefersLargeTitles, animated: false)
|
||||
} else {
|
||||
largeTitlesSwicth.isEnabled = false
|
||||
}
|
||||
let isTranslucent = navigationController!.navigationBar.isTranslucent
|
||||
translucentSwicth.setOn(isTranslucent, animated: false)
|
||||
}
|
||||
|
||||
@IBAction func toggleLargeTitle(_ sender: UISwitch) {
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
|
||||
}
|
||||
}
|
||||
@IBAction func toggleTranslucent(_ sender: UISwitch) {
|
||||
navigationController?.navigationBar.isTranslucent = sender.isOn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class TabBarViewController: UITabBarController {}
|
||||
|
||||
final class TabBarContentViewController: UIViewController {
|
||||
enum Tab3Mode {
|
||||
case changeOffset
|
||||
case changeAutoLayout
|
||||
var label: String {
|
||||
switch self {
|
||||
case .changeAutoLayout: return "Use AutoLayout(OK)"
|
||||
case .changeOffset: return "Use ContentOffset(NG)"
|
||||
}
|
||||
}
|
||||
}
|
||||
lazy var fpc = FloatingPanelController()
|
||||
var consoleVC: DebugTextViewController!
|
||||
|
||||
var threeLayout: ThreeTabBarPanelLayout!
|
||||
var tab3Mode: Tab3Mode = .changeAutoLayout
|
||||
var switcherLabel: UILabel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
fpc.delegate = self
|
||||
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
fpc.surfaceView.appearance = appearance
|
||||
|
||||
// Set a content view controller and track the scroll view
|
||||
let consoleVC = storyboard?.instantiateViewController(withIdentifier: "ConsoleViewController") as! DebugTextViewController
|
||||
fpc.set(contentViewController: consoleVC)
|
||||
consoleVC.textView.delegate = self // MUST call it before fpc.track(scrollView:)
|
||||
fpc.track(scrollView: consoleVC.textView)
|
||||
self.consoleVC = consoleVC
|
||||
|
||||
// Add FloatingPanel to self.view
|
||||
fpc.addPanel(toParent: self)
|
||||
|
||||
|
||||
if #available(iOS 15, *) {
|
||||
tabBarController?.tabBar.scrollEdgeAppearance = UITabBarAppearance()
|
||||
}
|
||||
|
||||
switch tabBarItem.tag {
|
||||
case 1:
|
||||
fpc.behavior = TwoTabBarPanelBehavior()
|
||||
case 2:
|
||||
let switcher = UISwitch()
|
||||
fpc.view.addSubview(switcher)
|
||||
switcher.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
switcher.bottomAnchor.constraint(equalTo: fpc.surfaceView.topAnchor, constant: -16.0),
|
||||
switcher.rightAnchor.constraint(equalTo: fpc.surfaceView.rightAnchor, constant: -16.0),
|
||||
])
|
||||
switcher.isOn = true
|
||||
switcher.tintColor = .white
|
||||
switcher.backgroundColor = .white
|
||||
switcher.layer.cornerRadius = 16.0
|
||||
switcher.addTarget(self,
|
||||
action: #selector(changeTab3Mode(_:)),
|
||||
for: .valueChanged)
|
||||
let label = UILabel()
|
||||
label.text = tab3Mode.label
|
||||
fpc.view.addSubview(label)
|
||||
switcherLabel = label
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
label.centerYAnchor.constraint(equalTo: switcher.centerYAnchor, constant: 0.0),
|
||||
label.rightAnchor.constraint(equalTo: switcher.leftAnchor, constant: -16.0),
|
||||
])
|
||||
|
||||
// Turn off the mask instead of content inset change
|
||||
consoleVC.textView.clipsToBounds = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
fpc.invalidateLayout()
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
@IBAction func close(sender: UIButton) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@objc
|
||||
private func changeTab3Mode(_ sender: UISwitch) {
|
||||
if sender.isOn {
|
||||
tab3Mode = .changeAutoLayout
|
||||
} else {
|
||||
tab3Mode = .changeOffset
|
||||
}
|
||||
switcherLabel.text = tab3Mode.label
|
||||
}
|
||||
}
|
||||
|
||||
extension TabBarContentViewController: UITextViewDelegate {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard self.tabBarItem.tag == 2 else { return }
|
||||
// Reset an invalid content offset by a user after updating the layout
|
||||
// of `consoleVC.textView`.
|
||||
// NOTE: FloatingPanel doesn't implicitly reset the offset(i.e.
|
||||
// Using KVO of `scrollView.contentOffset`). Because it can lead to an
|
||||
// infinite loop if a user also resets a content offset as below and,
|
||||
// in the situation, a user has to modify the library.
|
||||
if fpc.state != .full, fpc.surfaceLocation.y > fpc.surfaceLocation(for: .full).y {
|
||||
scrollView.contentOffset = .zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TabBarContentViewController: FloatingPanelControllerDelegate {
|
||||
// MARK: - FloatingPanel
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
switch self.tabBarItem.tag {
|
||||
case 0:
|
||||
return OneTabBarPanelLayout()
|
||||
case 1:
|
||||
return TwoTabBarPanelLayout()
|
||||
case 2:
|
||||
threeLayout = ThreeTabBarPanelLayout(parent: self)
|
||||
return threeLayout
|
||||
default:
|
||||
return FloatingPanelBottomLayout()
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) {
|
||||
guard self.tabBarItem.tag == 2 else { return }
|
||||
switch tab3Mode {
|
||||
case .changeAutoLayout:
|
||||
/* Good solution: Manipulate top constraint */
|
||||
assert(consoleVC.textViewTopConstraint != nil)
|
||||
let safeAreaTop = vc.layoutInsets.top
|
||||
if vc.surfaceLocation.y + threeLayout.topPadding < safeAreaTop {
|
||||
consoleVC.textViewTopConstraint?.constant = min(safeAreaTop - vc.surfaceLocation.y,
|
||||
safeAreaTop)
|
||||
} else {
|
||||
consoleVC.textViewTopConstraint?.constant = threeLayout.topPadding
|
||||
}
|
||||
case .changeOffset:
|
||||
/*
|
||||
Bad solution: Manipulate scroll content inset
|
||||
|
||||
FloatingPanelController keeps a content offset in moving a panel
|
||||
so that changing content inset or offset causes a buggy behavior.
|
||||
*/
|
||||
guard let scrollView = consoleVC.textView else { return }
|
||||
var insets = vc.adjustedContentInsets
|
||||
if vc.surfaceView.frame.minY < vc.layoutInsets.top {
|
||||
insets.top = vc.layoutInsets.top - vc.surfaceView.frame.minY
|
||||
} else {
|
||||
insets.top = 0.0
|
||||
}
|
||||
scrollView.contentInset = insets
|
||||
|
||||
if vc.surfaceView.frame.minY > 0 {
|
||||
scrollView.contentOffset = CGPoint(x: 0.0,
|
||||
y: 0.0 - scrollView.contentInset.top)
|
||||
}
|
||||
}
|
||||
|
||||
if vc.surfaceLocation.y > vc.surfaceLocation(for: .half).y {
|
||||
let progress = (vc.surfaceLocation.y - vc.surfaceLocation(for: .half).y)
|
||||
/ (vc.surfaceLocation(for: .tip).y - vc.surfaceLocation(for: .half).y)
|
||||
threeLayout.leftConstraint.constant = max(min(progress, 1.0), 0.0) * threeLayout.sideMargin
|
||||
threeLayout.rightConstraint.constant = -max(min(progress, 1.0), 0.0) * threeLayout.sideMargin
|
||||
} else {
|
||||
threeLayout.leftConstraint.constant = 0.0
|
||||
threeLayout.rightConstraint.constant = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OneTabBarPanelLayout: FloatingPanelLayout {
|
||||
var initialState: FloatingPanelState { .tip }
|
||||
var position: FloatingPanelPosition { .bottom }
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 22.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class TwoTabBarPanelLayout: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .half
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 261.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
|
||||
class TwoTabBarPanelBehavior: FloatingPanelBehavior {
|
||||
func allowsRubberBanding(for edges: UIRectEdge) -> Bool {
|
||||
return [UIRectEdge.top, UIRectEdge.bottom].contains(edges)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ThreeTabBarPanelLayout: FloatingPanelLayout {
|
||||
weak var parentVC: UIViewController!
|
||||
|
||||
var leftConstraint: NSLayoutConstraint!
|
||||
var rightConstraint: NSLayoutConstraint!
|
||||
|
||||
let topPadding: CGFloat = 17.0
|
||||
let sideMargin: CGFloat = 16.0
|
||||
|
||||
init(parent: UIViewController) {
|
||||
parentVC = parent
|
||||
}
|
||||
|
||||
var initialState: FloatingPanelState { .half }
|
||||
var position: FloatingPanelPosition { .bottom }
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 261.0 + parentVC.layoutInsets.bottom, edge: .bottom, referenceGuide: .superview),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 88.0 + parentVC.layoutInsets.bottom, edge: .bottom, referenceGuide: .superview),
|
||||
]
|
||||
}
|
||||
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.3
|
||||
}
|
||||
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
if #available(iOS 11.0, *) {
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
|
||||
} else {
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0)
|
||||
}
|
||||
return [ leftConstraint, rightConstraint ]
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
import FloatingPanel
|
||||
|
||||
extension FloatingPanelState {
|
||||
static let lastQuart: FloatingPanelState = FloatingPanelState(rawValue: "lastQuart", order: 750)
|
||||
static let firstQuart: FloatingPanelState = FloatingPanelState(rawValue: "firstQuart", order: 250)
|
||||
}
|
||||
|
||||
class FloatingPanelLayoutWithCustomState: FloatingPanelBottomLayout {
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
.lastQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.75, edge: .bottom, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
.firstQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.25, edge: .bottom, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class MainViewController: UIViewController {
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
private var observations: [NSKeyValueObservation] = []
|
||||
private lazy var useCaseController = UseCaseController(mainVC: self)
|
||||
}
|
||||
|
||||
extension MainViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
|
||||
automaticallyAdjustsScrollViewInsets = false
|
||||
|
||||
let searchController = UISearchController(searchResultsController: nil)
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationItem.searchController = searchController
|
||||
navigationItem.hidesSearchBarWhenScrolling = false
|
||||
navigationItem.largeTitleDisplayMode = .automatic
|
||||
}
|
||||
var insets = UIEdgeInsets.zero
|
||||
insets.bottom += 69.0
|
||||
tableView.contentInset = insets
|
||||
|
||||
// Show the initial panel
|
||||
useCaseController.set(useCase: .trackingTableView)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
if #available(iOS 11.0, *) {
|
||||
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
|
||||
self.tableView.reloadData()
|
||||
}) {
|
||||
observations.append(observation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
observations.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
extension MainViewController {
|
||||
@IBAction func showDebugMenu(_ sender: UIBarButtonItem) {
|
||||
useCaseController.setUpSettingsPanel(for: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension MainViewController: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if #available(iOS 11.0, *) {
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return UseCase.allCases.count + 30
|
||||
} else {
|
||||
return UseCase.allCases.count
|
||||
}
|
||||
} else {
|
||||
return UseCase.allCases.count
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
if UseCase.allCases.count > indexPath.row {
|
||||
let menu = UseCase.allCases[indexPath.row]
|
||||
cell.textLabel?.text = menu.name
|
||||
} else {
|
||||
cell.textLabel?.text = "\(indexPath.row) row"
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension MainViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard UseCase.allCases.count > indexPath.row else { return }
|
||||
|
||||
// Change panels
|
||||
useCaseController.set(useCase: UseCase.allCases[indexPath.row])
|
||||
}
|
||||
|
||||
@objc func dismissPresentedVC() {
|
||||
self.presentedViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
/**
|
||||
- Attention: `FloatingPanelLayout` must not be applied by the parent view
|
||||
controller of a panel. But here `MainViewController` adopts it
|
||||
purposely to check if the library prints an appropriate warning.
|
||||
*/
|
||||
extension MainViewController: FloatingPanelLayout {
|
||||
var position: FloatingPanelPosition { .bottom }
|
||||
var initialState: FloatingPanelState { .half }
|
||||
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class TopPositionedPanelLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .top
|
||||
let initialState: FloatingPanelState = .full
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 88.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .top, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
|
||||
class IntrinsicPanelLayout: FloatingPanelBottomLayout {
|
||||
override var initialState: FloatingPanelState { .full }
|
||||
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class RemovablePanelLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 130.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.3
|
||||
}
|
||||
}
|
||||
|
||||
class RemovablePanelLandscapeLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .full
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.3
|
||||
}
|
||||
}
|
||||
|
||||
class ModalPanelLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .full
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
]
|
||||
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.3
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -1,12 +1,12 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/19.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
final class CloseButton: UIButton {
|
||||
override var isHighlighted: Bool { didSet { setNeedsDisplay() } }
|
||||
override var isSelected: Bool { didSet { setNeedsDisplay() } }
|
||||
|
||||
class CloseButton: UIButton {
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
render()
|
||||
@@ -20,11 +20,14 @@ final class CloseButton: UIButton {
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
func p(_ p: CGFloat) -> CGFloat {
|
||||
return p * (2.0 / 3.0)
|
||||
}
|
||||
func p(_ p: CGFloat) -> CGFloat {
|
||||
return p * (2.0 / 3.0)
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool { didSet { setNeedsDisplay() } }
|
||||
override var isSelected: Bool { didSet { setNeedsDisplay() } }
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
context.setLineWidth(p(1.0))
|
||||
@@ -71,7 +74,7 @@ final class CloseButton: UIButton {
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
final class SafeAreaView: UIView {
|
||||
class SafeAreaView: UIView {
|
||||
override func prepareForInterfaceBuilder() {
|
||||
let label = UILabel()
|
||||
label.text = "Safe Area"
|
||||
@@ -86,7 +89,7 @@ final class SafeAreaView: UIView {
|
||||
|
||||
|
||||
@IBDesignable
|
||||
final class OnSafeAreaView: UIView {
|
||||
class OnSafeAreaView: UIView {
|
||||
override func prepareForInterfaceBuilder() {
|
||||
let label = UILabel()
|
||||
label.text = "On Safe Area"
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/10/08.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class PagePanelController: NSObject {
|
||||
var pages: [UIViewController] = []
|
||||
}
|
||||
|
||||
extension PagePanelController {
|
||||
func makePageViewControllerForContent() -> UIPageViewController {
|
||||
pages = [DebugTableViewController(), DebugTableViewController(), DebugTableViewController()]
|
||||
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
|
||||
pageVC.dataSource = self
|
||||
pageVC.delegate = self
|
||||
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
|
||||
return pageVC
|
||||
}
|
||||
|
||||
func makePageViewController(for vc: MainViewController) -> UIPageViewController {
|
||||
pages = [UIColor.blue, .red, .green].compactMap({ (color) -> UIViewController in
|
||||
let page = FloatingPanelController(delegate: self)
|
||||
page.view.backgroundColor = color
|
||||
page.panGestureRecognizer.delegateProxy = self
|
||||
page.show()
|
||||
return page
|
||||
})
|
||||
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
|
||||
let closeButton = UIButton(type: .custom)
|
||||
pageVC.view.addSubview(closeButton)
|
||||
closeButton.setTitle("Close", for: .normal)
|
||||
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
closeButton.addTarget(vc, action: #selector(MainViewController.dismissPresentedVC), for: .touchUpInside)
|
||||
NSLayoutConstraint.activate([
|
||||
closeButton.topAnchor.constraint(equalTo: pageVC.layoutGuide.topAnchor, constant: 16.0),
|
||||
closeButton.leftAnchor.constraint(equalTo: pageVC.view.leftAnchor, constant: 16.0),
|
||||
])
|
||||
pageVC.dataSource = self
|
||||
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
|
||||
pageVC.modalPresentationStyle = .fullScreen
|
||||
return pageVC
|
||||
}
|
||||
}
|
||||
|
||||
extension PagePanelController: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
return FloatingPanelBottomLayout()
|
||||
}
|
||||
}
|
||||
|
||||
extension PagePanelController: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension PagePanelController: UIPageViewControllerDataSource {
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let index = pages.firstIndex(of: viewController),
|
||||
index + 1 < pages.count
|
||||
else { return nil }
|
||||
return pages[index + 1]
|
||||
}
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let index = pages.firstIndex(of: viewController),
|
||||
index - 1 >= 0
|
||||
else { return nil }
|
||||
return pages[index - 1]
|
||||
}
|
||||
}
|
||||
|
||||
extension PagePanelController: UIPageViewControllerDelegate {
|
||||
// For showPageContent
|
||||
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
|
||||
if completed, let page = pageViewController.viewControllers?.first {
|
||||
(pageViewController.parent as! FloatingPanelController).track(scrollView: (page as! DebugTableViewController).tableView)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
|
||||
enum UseCase: Int, CaseIterable {
|
||||
case trackingTableView
|
||||
case trackingTextView
|
||||
case showDetail
|
||||
case showModal
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
case showNestedScrollView
|
||||
case showRemovablePanel
|
||||
case showIntrinsicView
|
||||
case showContentInset
|
||||
case showContainerMargins
|
||||
case showNavigationController
|
||||
case showTopPositionedPanel
|
||||
case showAdaptivePanel
|
||||
case showAdaptivePanelWithCustomGuide
|
||||
case showCustomStatePanel
|
||||
}
|
||||
|
||||
extension UseCase {
|
||||
var name: String {
|
||||
switch self {
|
||||
case .trackingTableView: return "Scroll tracking(TableView)"
|
||||
case .trackingTextView: return "Scroll tracking(TextView)"
|
||||
case .showDetail: return "Show Detail Panel"
|
||||
case .showModal: return "Show Modal"
|
||||
case .showPanelModal: return "Show Panel Modal"
|
||||
case .showMultiPanelModal: return "Show Multi Panel Modal"
|
||||
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
|
||||
case .showTabBar: return "Show Tab Bar"
|
||||
case .showPageView: return "Show Page View"
|
||||
case .showPageContentView: return "Show Page Content View"
|
||||
case .showNestedScrollView: return "Show Nested ScrollView"
|
||||
case .showRemovablePanel: return "Show Removable Panel"
|
||||
case .showIntrinsicView: return "Show Intrinsic View"
|
||||
case .showContentInset: return "Show with ContentInset"
|
||||
case .showContainerMargins: return "Show with ContainerMargins"
|
||||
case .showNavigationController: return "Show Navigation Controller"
|
||||
case .showTopPositionedPanel: return "Show Top Positioned Panel"
|
||||
case .showAdaptivePanel: return "Show Adaptive Panel"
|
||||
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
|
||||
case .showCustomStatePanel: return "Show Panel with Custom state"
|
||||
}
|
||||
}
|
||||
|
||||
private enum Content {
|
||||
case storyboard(String)
|
||||
case viewController(UIViewController)
|
||||
}
|
||||
|
||||
private var content: Content {
|
||||
switch self {
|
||||
case .trackingTableView: return .viewController(DebugTableViewController())
|
||||
case .trackingTextView: return .storyboard("ConsoleViewController") // Storyboard only
|
||||
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showModal: return .storyboard(String(describing: ModalViewController.self))
|
||||
case .showMultiPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
|
||||
case .showPageView: return .viewController(DebugTableViewController())
|
||||
case .showPageContentView: return .viewController(DebugTableViewController())
|
||||
case .showNestedScrollView: return .storyboard(String(describing: NestedScrollViewController.self))
|
||||
case .showRemovablePanel: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showIntrinsicView: return .storyboard("IntrinsicViewController") // Storyboard only
|
||||
case .showContentInset: return .viewController(DebugTableViewController())
|
||||
case .showContainerMargins: return .viewController(DebugTableViewController())
|
||||
case .showNavigationController: return .storyboard("RootNavigationController") // Storyboard only
|
||||
case .showTopPositionedPanel: return .viewController(DebugTableViewController())
|
||||
case .showAdaptivePanel: return .storyboard(String(describing: ImageViewController.self))
|
||||
case .showAdaptivePanelWithCustomGuide: return .storyboard(String(describing: ImageViewController.self))
|
||||
case .showCustomStatePanel: return .viewController(DebugTableViewController())
|
||||
}
|
||||
}
|
||||
|
||||
func makeContentViewController(with storyboard: UIStoryboard) -> UIViewController {
|
||||
switch content {
|
||||
case .storyboard(let id):
|
||||
return storyboard.instantiateViewController(withIdentifier: id)
|
||||
case .viewController(let vc):
|
||||
return vc
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class UseCaseController: NSObject {
|
||||
unowned let mainVC: MainViewController
|
||||
private(set) var useCase: UseCase
|
||||
|
||||
private var mainPanelVC: FloatingPanelController!
|
||||
private var detailPanelVC: FloatingPanelController!
|
||||
private var settingsPanelVC: FloatingPanelController!
|
||||
private lazy var pagePanelController = PagePanelController()
|
||||
|
||||
init(mainVC: MainViewController) {
|
||||
self.mainVC = mainVC
|
||||
self.useCase = .trackingTableView
|
||||
}
|
||||
}
|
||||
|
||||
extension UseCaseController {
|
||||
func set(useCase: UseCase) {
|
||||
self.useCase = useCase
|
||||
|
||||
let contentVC = useCase.makeContentViewController(with: mainVC.storyboard!)
|
||||
|
||||
if let fpc = detailPanelVC {
|
||||
fpc.removePanelFromParent(animated: true, completion: nil)
|
||||
self.detailPanelVC = nil
|
||||
}
|
||||
|
||||
switch useCase {
|
||||
case .trackingTableView:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(UseCaseController.handleSurface(tapGesture:)))
|
||||
tapGesture.cancelsTouchesInView = false
|
||||
tapGesture.numberOfTapsRequired = 2
|
||||
// Prevents a delay to response a tap in menus of DebugTableViewController.
|
||||
tapGesture.delaysTouchesEnded = false
|
||||
fpc.surfaceView.addGestureRecognizer(tapGesture)
|
||||
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .trackingTextView:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showDetail:
|
||||
// Initialize FloatingPanelController
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
// Set a content view controller
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.contentMode = .fitToBounds
|
||||
(contentVC as? DetailViewController)?.intrinsicHeightConstraint.isActive = false
|
||||
|
||||
detailPanelVC = fpc
|
||||
// Add FloatingPanel to self.view
|
||||
fpc.addPanel(toParent: mainVC, animated: true)
|
||||
|
||||
case .showModal, .showTabBar:
|
||||
let modalVC = contentVC
|
||||
modalVC.modalPresentationStyle = .fullScreen
|
||||
mainVC.present(modalVC, animated: true, completion: nil)
|
||||
|
||||
case .showPageView:
|
||||
let pageVC = pagePanelController.makePageViewController(for: mainVC)
|
||||
mainVC.present(pageVC, animated: true, completion: nil)
|
||||
|
||||
case .showPageContentView:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
let pageVC = pagePanelController.makePageViewControllerForContent()
|
||||
if let page = (fpc.contentViewController as? UIPageViewController)?.viewControllers?.first {
|
||||
fpc.track(scrollView: (page as! DebugTableViewController).tableView)
|
||||
}
|
||||
fpc.set(contentViewController: pageVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showRemovablePanel, .showIntrinsicView:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
fpc.delegate = self
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showNestedScrollView:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.panGestureRecognizer.delegateProxy = self
|
||||
fpc.delegate = self
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showPanelModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = mainVC.storyboard!.instantiateViewController(withIdentifier: "DetailViewController")
|
||||
contentVC.loadViewIfNeeded()
|
||||
(contentVC as? DetailViewController)?.modeChangeView.isHidden = true
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.delegate = self
|
||||
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 38.5
|
||||
fpc.surfaceView.appearance = appearance
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showMultiPanelModal:
|
||||
let fpc = MultiPanelController()
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showPanelInSheetModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = UIViewController()
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.delegate = self
|
||||
|
||||
let apprearance = SurfaceAppearance()
|
||||
apprearance.cornerRadius = 38.5
|
||||
apprearance.shadows = []
|
||||
fpc.surfaceView.appearance = apprearance
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
|
||||
let mvc = UIViewController()
|
||||
mvc.view.backgroundColor = UIColor(displayP3Red: 2/255, green: 184/255, blue: 117/255, alpha: 1.0)
|
||||
fpc.addPanel(toParent: mvc)
|
||||
mainVC.present(mvc, animated: true, completion: nil)
|
||||
|
||||
case .showContentInset:
|
||||
let contentViewController = UIViewController()
|
||||
contentViewController.view.backgroundColor = .green
|
||||
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.set(contentViewController: contentViewController)
|
||||
fpc.surfaceView.contentPadding = .init(top: 20, left: 20, bottom: 20, right: 20)
|
||||
|
||||
fpc.delegate = self
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showContainerMargins:
|
||||
let fpc = FloatingPanelController()
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 38.5
|
||||
fpc.surfaceView.appearance = appearance
|
||||
|
||||
fpc.surfaceView.backgroundColor = .red
|
||||
fpc.surfaceView.containerMargins = .init(top: 24.0, left: 8.0, bottom: max(mainVC.layoutInsets.bottom, 8.0), right: 8.0)
|
||||
#if swift(>=5.1) // Actually Xcode 11 or later
|
||||
if #available(iOS 13.0, *) {
|
||||
fpc.surfaceView.layer.cornerCurve = .continuous
|
||||
}
|
||||
#endif
|
||||
|
||||
fpc.delegate = self
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showNavigationController:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.contentInsetAdjustmentBehavior = .never
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showTopPositionedPanel: // For debug
|
||||
let fpc = FloatingPanelController(delegate: self)
|
||||
let contentVC = UIViewController()
|
||||
contentVC.view.backgroundColor = .red
|
||||
fpc.set(contentViewController: contentVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showAdaptivePanel, .showAdaptivePanelWithCustomGuide:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
if case let contentVC as ImageViewController = contentVC {
|
||||
if #available(iOS 11.0, *) {
|
||||
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithCustomGuide) ? .withHeaderFooter : .onlyImage
|
||||
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
|
||||
fpc.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
|
||||
} else {
|
||||
fpc.layout = ImageViewController.PanelLayout(targetGuide: nil)
|
||||
}
|
||||
}
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showCustomStatePanel:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
addMain(panel: fpc)
|
||||
}
|
||||
}
|
||||
|
||||
func setUpSettingsPanel(for mainVC: MainViewController) {
|
||||
guard settingsPanelVC == nil else { return }
|
||||
// Initialize FloatingPanelController
|
||||
settingsPanelVC = FloatingPanelController()
|
||||
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
settingsPanelVC.surfaceView.appearance = appearance
|
||||
|
||||
settingsPanelVC.isRemovalInteractionEnabled = true
|
||||
settingsPanelVC.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
settingsPanelVC.delegate = self
|
||||
|
||||
let contentVC = mainVC.storyboard?.instantiateViewController(withIdentifier: "SettingsViewController")
|
||||
|
||||
// Set a content view controller
|
||||
settingsPanelVC.set(contentViewController: contentVC)
|
||||
|
||||
// Add FloatingPanel to self.view
|
||||
settingsPanelVC.addPanel(toParent: mainVC, animated: true)
|
||||
}
|
||||
|
||||
private func addMain(panel fpc: FloatingPanelController) {
|
||||
let oldMainPanelVC = mainPanelVC
|
||||
mainPanelVC = fpc
|
||||
if let oldMainPanelVC = oldMainPanelVC {
|
||||
oldMainPanelVC.removePanelFromParent(animated: true, completion: {
|
||||
self.mainPanelVC.addPanel(toParent: self.mainVC, animated: true)
|
||||
})
|
||||
} else {
|
||||
mainPanelVC.addPanel(toParent: mainVC, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
private func handleSurface(tapGesture: UITapGestureRecognizer) {
|
||||
switch mainPanelVC.state {
|
||||
case .full:
|
||||
mainPanelVC.move(to: .half, animated: true)
|
||||
default:
|
||||
mainPanelVC.move(to: .full, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UseCaseController: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint {
|
||||
if useCase == .showNavigationController, #available(iOS 11.0, *) {
|
||||
// 148.0 is the SafeArea's top value for a navigation bar with a large title.
|
||||
return CGPoint(x: 0.0, y: 0.0 - trackingScrollView.contentInset.top - 148.0)
|
||||
}
|
||||
return CGPoint(x: 0.0, y: 0.0 - trackingScrollView.contentInset.top)
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
if vc == settingsPanelVC {
|
||||
return IntrinsicPanelLayout()
|
||||
}
|
||||
|
||||
switch useCase {
|
||||
case .showTopPositionedPanel:
|
||||
return TopPositionedPanelLayout()
|
||||
case .showRemovablePanel:
|
||||
return newCollection.verticalSizeClass == .compact ? RemovablePanelLandscapeLayout() : RemovablePanelLayout()
|
||||
case .showIntrinsicView:
|
||||
return IntrinsicPanelLayout()
|
||||
case .showPanelModal:
|
||||
if vc != mainPanelVC && vc != detailPanelVC {
|
||||
return ModalPanelLayout()
|
||||
}
|
||||
fallthrough
|
||||
case .showContentInset:
|
||||
return FloatingPanelBottomLayout()
|
||||
case .showCustomStatePanel:
|
||||
return FloatingPanelLayoutWithCustomState()
|
||||
default:
|
||||
return (newCollection.verticalSizeClass == .compact) ? FloatingPanelBottomLayout() : mainVC
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelDidRemove(_ vc: FloatingPanelController) {
|
||||
switch vc {
|
||||
case settingsPanelVC:
|
||||
settingsPanelVC = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UseCaseController: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if case .showNestedScrollView = useCase {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private extension FloatingPanelController {
|
||||
func ext_trackScrollView(in contentVC: UIViewController) {
|
||||
switch contentVC {
|
||||
case let consoleVC as DebugTextViewController:
|
||||
track(scrollView: consoleVC.textView)
|
||||
|
||||
case let contentVC as DebugTableViewController:
|
||||
let ob = contentVC.tableView.observe(\.isEditing) { [weak self] (tableView, _) in
|
||||
self?.panGestureRecognizer.isEnabled = !tableView.isEditing
|
||||
}
|
||||
contentVC.kvoObservers.append(ob)
|
||||
track(scrollView: contentVC.tableView)
|
||||
|
||||
case let contentVC as NestedScrollViewController:
|
||||
track(scrollView: contentVC.scrollView)
|
||||
|
||||
case let navVC as UINavigationController:
|
||||
if let rootVC = (navVC.topViewController as? MainViewController) {
|
||||
rootVC.loadViewIfNeeded()
|
||||
track(scrollView: rootVC.tableView)
|
||||
}
|
||||
|
||||
case let contentVC as ImageViewController:
|
||||
track(scrollView: contentVC.scrollView)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,10 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// FloatingModalSampleTests.swift
|
||||
// FloatingModalSampleTests
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanelSample
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
|
||||
@@ -1,397 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
545BA70621BA3214007F7846 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA70521BA3214007F7846 /* AppDelegate.m */; };
|
||||
545BA70921BA3214007F7846 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA70821BA3214007F7846 /* MainViewController.m */; };
|
||||
545BA70C21BA3214007F7846 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545BA70A21BA3214007F7846 /* Main.storyboard */; };
|
||||
545BA70E21BA3217007F7846 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 545BA70D21BA3217007F7846 /* Assets.xcassets */; };
|
||||
545BA71121BA3217007F7846 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545BA70F21BA3217007F7846 /* LaunchScreen.storyboard */; };
|
||||
545BA71421BA3217007F7846 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA71321BA3217007F7846 /* main.m */; };
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; };
|
||||
545BA72721BA3BAF007F7846 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
545BA72821BA3BAF007F7846 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
545BA72721BA3BAF007F7846 /* FloatingPanel.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
545BA70121BA3214007F7846 /* SamplesObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SamplesObjC.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545BA70421BA3214007F7846 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
545BA70521BA3214007F7846 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
545BA70721BA3214007F7846 /* MainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = "<group>"; };
|
||||
545BA70821BA3214007F7846 /* MainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = "<group>"; };
|
||||
545BA70B21BA3214007F7846 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
545BA70D21BA3217007F7846 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
545BA71021BA3217007F7846 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
545BA71221BA3217007F7846 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545BA71321BA3217007F7846 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
545BA72221BA3867007F7846 /* SamplesObjC-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SamplesObjC-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
545BA72521BA3BAF007F7846 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
545BA6FE21BA3214007F7846 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
545BA6F821BA3214007F7846 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545BA72521BA3BAF007F7846 /* FloatingPanel.framework */,
|
||||
545BA70321BA3214007F7846 /* SamplesObjC */,
|
||||
545BA70221BA3214007F7846 /* Products */,
|
||||
5D82A6AE28D18443006A44BA /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
545BA70221BA3214007F7846 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545BA70121BA3214007F7846 /* SamplesObjC.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
545BA70321BA3214007F7846 /* SamplesObjC */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545BA70D21BA3217007F7846 /* Assets.xcassets */,
|
||||
545BA70F21BA3217007F7846 /* LaunchScreen.storyboard */,
|
||||
545BA70A21BA3214007F7846 /* Main.storyboard */,
|
||||
545BA70421BA3214007F7846 /* AppDelegate.h */,
|
||||
545BA70521BA3214007F7846 /* AppDelegate.m */,
|
||||
545BA70721BA3214007F7846 /* MainViewController.h */,
|
||||
545BA70821BA3214007F7846 /* MainViewController.m */,
|
||||
545BA71221BA3217007F7846 /* Info.plist */,
|
||||
545BA71321BA3217007F7846 /* main.m */,
|
||||
545BA72221BA3867007F7846 /* SamplesObjC-Bridging-Header.h */,
|
||||
);
|
||||
path = SamplesObjC;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5D82A6AE28D18443006A44BA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
545BA70021BA3214007F7846 /* SamplesObjC */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 545BA71721BA3217007F7846 /* Build configuration list for PBXNativeTarget "SamplesObjC" */;
|
||||
buildPhases = (
|
||||
545BA6FD21BA3214007F7846 /* Sources */,
|
||||
545BA6FE21BA3214007F7846 /* Frameworks */,
|
||||
545BA6FF21BA3214007F7846 /* Resources */,
|
||||
545BA72821BA3BAF007F7846 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SamplesObjC;
|
||||
productName = SamplesObjC;
|
||||
productReference = 545BA70121BA3214007F7846 /* SamplesObjC.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
545BA6F921BA3214007F7846 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
ORGANIZATIONNAME = "Shin Yamamoto";
|
||||
TargetAttributes = {
|
||||
545BA70021BA3214007F7846 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
LastSwiftMigration = 1010;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 545BA6FC21BA3214007F7846 /* Build configuration list for PBXProject "SamplesObjC" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 545BA6F821BA3214007F7846;
|
||||
productRefGroup = 545BA70221BA3214007F7846 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
545BA70021BA3214007F7846 /* SamplesObjC */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
545BA6FF21BA3214007F7846 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545BA71121BA3217007F7846 /* LaunchScreen.storyboard in Resources */,
|
||||
545BA70E21BA3217007F7846 /* Assets.xcassets in Resources */,
|
||||
545BA70C21BA3214007F7846 /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
545BA6FD21BA3214007F7846 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545BA70921BA3214007F7846 /* MainViewController.m in Sources */,
|
||||
545BA71421BA3217007F7846 /* main.m in Sources */,
|
||||
545BA70621BA3214007F7846 /* AppDelegate.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
545BA70A21BA3214007F7846 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
545BA70B21BA3214007F7846 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
545BA70F21BA3217007F7846 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
545BA71021BA3217007F7846 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
545BA71521BA3217007F7846 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
545BA71621BA3217007F7846 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
545BA71821BA3217007F7846 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = NO;
|
||||
DEVELOPMENT_TEAM = J3D7L9FHSS;
|
||||
INFOPLIST_FILE = SamplesObjC/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.SamplesObjC;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "SamplesObjC/SamplesObjC-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
545BA71921BA3217007F7846 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = NO;
|
||||
DEVELOPMENT_TEAM = J3D7L9FHSS;
|
||||
INFOPLIST_FILE = SamplesObjC/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.SamplesObjC;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "SamplesObjC/SamplesObjC-Bridging-Header.h";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
545BA6FC21BA3214007F7846 /* Build configuration list for PBXProject "SamplesObjC" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545BA71521BA3217007F7846 /* Debug */,
|
||||
545BA71621BA3217007F7846 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
545BA71721BA3217007F7846 /* Build configuration list for PBXNativeTarget "SamplesObjC" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545BA71821BA3217007F7846 /* Debug */,
|
||||
545BA71921BA3217007F7846 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 545BA6F921BA3214007F7846 /* Project object */;
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545BA70021BA3214007F7846"
|
||||
BuildableName = "SamplesObjC.app"
|
||||
BlueprintName = "SamplesObjC"
|
||||
ReferencedContainer = "container:SamplesObjC.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545BA70021BA3214007F7846"
|
||||
BuildableName = "SamplesObjC.app"
|
||||
BlueprintName = "SamplesObjC"
|
||||
ReferencedContainer = "container:SamplesObjC.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545BA70021BA3214007F7846"
|
||||
BuildableName = "SamplesObjC.app"
|
||||
BlueprintName = "SamplesObjC"
|
||||
ReferencedContainer = "container:SamplesObjC.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545BA70021BA3214007F7846"
|
||||
BuildableName = "SamplesObjC.app"
|
||||
BlueprintName = "SamplesObjC"
|
||||
ReferencedContainer = "container:SamplesObjC.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow* window;
|
||||
|
||||
@end
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
@interface AppDelegate ()
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
@end
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Main View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="MainViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,45 +0,0 @@
|
||||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@import FloatingPanel;
|
||||
|
||||
@interface MainViewController : UIViewController
|
||||
@end
|
||||
|
||||
@interface MyFloatingPanelLayout : NSObject <FloatingPanelLayout>
|
||||
@end
|
||||
|
||||
@interface MyFloatingPanelBehavior : NSObject <FloatingPanelBehavior>
|
||||
@end
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
#import "MainViewController.h"
|
||||
@import FloatingPanel;
|
||||
|
||||
// Defining a custom FloatingPanelState
|
||||
@interface FloatingPanelState(Extended)
|
||||
+ (FloatingPanelState *)LastQuart;
|
||||
@end
|
||||
|
||||
@implementation FloatingPanelState(Extended)
|
||||
static FloatingPanelState *_lastQuart;
|
||||
+ (FloatingPanelState *)LastQuart {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_lastQuart = [[FloatingPanelState alloc] initWithRawValue:@"lastquart" order:750];
|
||||
});
|
||||
return _lastQuart;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface MainViewController()<FloatingPanelControllerDelegate>
|
||||
@end
|
||||
|
||||
@implementation MainViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
FloatingPanelController *fpc = [[FloatingPanelController alloc] init];
|
||||
[fpc setContentViewController:nil];
|
||||
[fpc setDelegate:self];
|
||||
|
||||
[fpc setLayout: [MyFloatingPanelLayout new]];
|
||||
[fpc setBehavior:[MyFloatingPanelBehavior new]];
|
||||
[fpc setRemovalInteractionEnabled:NO];
|
||||
|
||||
[fpc addPanelToParent:self at:self.view.subviews.count animated:NO completion:nil];
|
||||
[fpc moveToState:FloatingPanelState.Tip animated:true completion:nil];
|
||||
|
||||
[self updateAppearance: fpc];
|
||||
}
|
||||
|
||||
- (id<FloatingPanelLayout>)floatingPanel:(FloatingPanelController *)vc layoutFor:(UITraitCollection *)newCollection {
|
||||
FloatingPanelBottomLayout *layout = [FloatingPanelBottomLayout new];
|
||||
return layout;
|
||||
}
|
||||
|
||||
- (void)updateAppearance: (FloatingPanelController*)fpc
|
||||
{
|
||||
FloatingPanelSurfaceAppearance *appearance = [[FloatingPanelSurfaceAppearance alloc] init];
|
||||
appearance.backgroundColor = [UIColor clearColor];
|
||||
appearance.cornerRadius = 23.0;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
fpc.surfaceView.containerView.layer.cornerCurve = kCACornerCurveContinuous;
|
||||
}
|
||||
FloatingPanelSurfaceAppearanceShadow *shadow = [[FloatingPanelSurfaceAppearanceShadow alloc] init];
|
||||
shadow.color = [UIColor redColor];
|
||||
shadow.radius = 10.0;
|
||||
shadow.spread = 10.0;
|
||||
FloatingPanelSurfaceAppearanceShadow *shadow2 = [[FloatingPanelSurfaceAppearanceShadow alloc] init];
|
||||
shadow2.color = [UIColor blueColor];
|
||||
shadow2.radius = 10.0;
|
||||
shadow2.spread = 10.0;
|
||||
|
||||
appearance.shadows = @[shadow, shadow2];
|
||||
fpc.surfaceView.appearance = appearance;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MyFloatingPanelLayout
|
||||
- (FloatingPanelState *)initialState {
|
||||
return FloatingPanelState.Half;
|
||||
}
|
||||
- (NSDictionary<FloatingPanelState *, id<FloatingPanelLayoutAnchoring>> *)anchors {
|
||||
return @{
|
||||
FloatingPanelState.LastQuart: [[FloatingPanelLayoutAnchor alloc] initWithFractionalInset:0.25
|
||||
edge:FloatingPanelReferenceEdgeTop
|
||||
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
|
||||
FloatingPanelState.Half: [[FloatingPanelLayoutAnchor alloc] initWithFractionalInset:0.5
|
||||
edge:FloatingPanelReferenceEdgeTop
|
||||
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
|
||||
FloatingPanelState.Tip: [[FloatingPanelLayoutAnchor alloc] initWithAbsoluteInset:44.0
|
||||
edge:FloatingPanelReferenceEdgeBottom
|
||||
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
|
||||
};
|
||||
}
|
||||
|
||||
- (enum FloatingPanelPosition)position {
|
||||
return FloatingPanelPositionBottom;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@implementation MyFloatingPanelBehavior
|
||||
@end
|
||||
@@ -1,3 +0,0 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,13 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5433F24B21717EA300BDAA5D /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5433F24A21717EA300BDAA5D /* FloatingPanel.framework */; };
|
||||
5433F24C21717EA300BDAA5D /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5433F24A21717EA300BDAA5D /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
548DF95421705BE00041922A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95321705BE00041922A /* AppDelegate.swift */; };
|
||||
548DF95621705BE00041922A /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95521705BE00041922A /* MainViewController.swift */; };
|
||||
548DF95621705BE00041922A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95521705BE00041922A /* ViewController.swift */; };
|
||||
548DF95921705BE00041922A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95721705BE00041922A /* Main.storyboard */; };
|
||||
548DF95B21705BE10041922A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95A21705BE10041922A /* Assets.xcassets */; };
|
||||
548DF95E21705BE10041922A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95C21705BE10041922A /* LaunchScreen.storyboard */; };
|
||||
549D23CF233C77CF008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */; };
|
||||
549D23D0233C77CF008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5D82A6AA28D18432006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6A928D1842B006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -24,7 +23,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
549D23D0233C77CF008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
|
||||
5433F24C21717EA300BDAA5D /* FloatingPanel.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -32,15 +31,14 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5433F24A21717EA300BDAA5D /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
548DF95021705BE00041922A /* Stocks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stocks.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
548DF95321705BE00041922A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
548DF95521705BE00041922A /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
||||
548DF95521705BE00041922A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
548DF95821705BE00041922A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
548DF95A21705BE10041922A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
548DF95D21705BE10041922A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
548DF95F21705BE10041922A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5D82A6A928D1842B006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -48,8 +46,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5D82A6AA28D18432006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
549D23CF233C77CF008EF4D7 /* FloatingPanel.framework in Frameworks */,
|
||||
5433F24B21717EA300BDAA5D /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -59,10 +56,9 @@
|
||||
548DF94721705BE00041922A = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */,
|
||||
5433F24A21717EA300BDAA5D /* FloatingPanel.framework */,
|
||||
548DF95221705BE00041922A /* Stocks */,
|
||||
548DF95121705BE00041922A /* Products */,
|
||||
5D82A6A828D1842A006A44BA /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -77,24 +73,16 @@
|
||||
548DF95221705BE00041922A /* Stocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
548DF95321705BE00041922A /* AppDelegate.swift */,
|
||||
548DF95521705BE00041922A /* ViewController.swift */,
|
||||
548DF95721705BE00041922A /* Main.storyboard */,
|
||||
548DF95A21705BE10041922A /* Assets.xcassets */,
|
||||
548DF95C21705BE10041922A /* LaunchScreen.storyboard */,
|
||||
548DF95721705BE00041922A /* Main.storyboard */,
|
||||
548DF95321705BE00041922A /* AppDelegate.swift */,
|
||||
548DF95521705BE00041922A /* MainViewController.swift */,
|
||||
548DF95F21705BE10041922A /* Info.plist */,
|
||||
);
|
||||
path = Stocks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5D82A6A828D1842A006A44BA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D82A6A928D1842B006A44BA /* libswiftCoreGraphics.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -123,7 +111,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1000;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = scenee;
|
||||
TargetAttributes = {
|
||||
548DF94F21705BE00041922A = {
|
||||
@@ -167,7 +155,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
548DF95621705BE00041922A /* MainViewController.swift in Sources */,
|
||||
548DF95621705BE00041922A /* ViewController.swift in Sources */,
|
||||
548DF95421705BE00041922A /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -220,7 +208,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -282,7 +269,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -324,11 +310,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -347,11 +329,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -27,6 +27,8 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@@ -36,8 +38,8 @@
|
||||
ReferencedContainer = "container:Stocks.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -59,6 +61,8 @@
|
||||
ReferencedContainer = "container:Stocks.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// Stocks
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/10/12.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Main View Controller-->
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="MainViewController" customModule="Stocks" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Stocks" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uop-sw-I6p">
|
||||
<rect key="frame" x="0.0" y="65" width="600" height="490.5"/>
|
||||
<rect key="frame" x="0.0" y="85" width="375" height="537.5"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" placeholderIntrinsicWidth="375" placeholderIntrinsicHeight="625" image="stocks_list" translatesAutoresizingMaskIntoConstraints="NO" id="XJR-iK-fem">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="625"/>
|
||||
@@ -30,10 +34,10 @@
|
||||
</constraints>
|
||||
</scrollView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dFl-81-6ok">
|
||||
<rect key="frame" x="0.0" y="555.5" width="600" height="44.5"/>
|
||||
<rect key="frame" x="0.0" y="622.5" width="375" height="44.5"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="yahoo_bottom_bar" translatesAutoresizingMaskIntoConstraints="NO" id="NKr-gS-mpx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44.5"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="44.5" id="B5t-ZF-qUj"/>
|
||||
@@ -49,10 +53,10 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="-8" translatesAutoresizingMaskIntoConstraints="NO" id="f7r-Al-pIN">
|
||||
<rect key="frame" x="16" y="0.0" width="153.5" height="57"/>
|
||||
<rect key="frame" x="16" y="20" width="153.5" height="57"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="STOCKS" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCG-Wl-fXa">
|
||||
<rect key="frame" x="0.0" y="0.0" width="111" height="32.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="111.5" height="32.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="27"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -66,7 +70,6 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" red="0.11764924973249435" green="0.11764311045408249" blue="0.11764728277921677" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="dFl-81-6ok" secondAttribute="trailing" id="20i-yz-AaQ"/>
|
||||
@@ -80,6 +83,7 @@
|
||||
<constraint firstItem="dFl-81-6ok" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="nlX-Ab-1aI"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="NKr-gS-mpx" secondAttribute="bottom" id="yeu-NH-Pmp"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="bottomToolView" destination="dFl-81-6ok" id="NXn-af-lFv"/>
|
||||
@@ -118,7 +122,6 @@
|
||||
</constraints>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="INo-op-FLO"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="h7M-7T-4k4" secondAttribute="bottom" id="2b8-Wo-tm0"/>
|
||||
@@ -126,6 +129,7 @@
|
||||
<constraint firstItem="h7M-7T-4k4" firstAttribute="trailing" secondItem="INo-op-FLO" secondAttribute="trailing" id="WTc-1l-3Ha"/>
|
||||
<constraint firstItem="h7M-7T-4k4" firstAttribute="leading" secondItem="INo-op-FLO" secondAttribute="leading" id="na8-TO-WiG"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="INo-op-FLO"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="600"/>
|
||||
<connections>
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
class MainViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
@IBOutlet var topBannerView: UIImageView!
|
||||
@IBOutlet weak var labelStackView: UIStackView!
|
||||
@IBOutlet weak var bottomToolView: UIView!
|
||||
|
||||
var fpc: FloatingPanelController!
|
||||
var newsVC: NewsViewController!
|
||||
|
||||
var initialColor: UIColor = .black
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
initialColor = view.backgroundColor!
|
||||
// Initialize FloatingPanelController
|
||||
fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
fpc.behavior = FloatingPanelStocksBehavior()
|
||||
|
||||
// Initialize FloatingPanelController and add the view
|
||||
fpc.surfaceView.backgroundColor = UIColor(displayP3Red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0)
|
||||
fpc.surfaceView.appearance.cornerRadius = 24.0
|
||||
fpc.surfaceView.appearance.shadows = []
|
||||
fpc.surfaceView.appearance.borderWidth = 1.0 / traitCollection.displayScale
|
||||
fpc.surfaceView.appearance.borderColor = UIColor.black.withAlphaComponent(0.2)
|
||||
|
||||
newsVC = storyboard?.instantiateViewController(withIdentifier: "News") as? NewsViewController
|
||||
|
||||
// Set a content view controller
|
||||
fpc.set(contentViewController: newsVC)
|
||||
fpc.track(scrollView: newsVC.scrollView)
|
||||
|
||||
fpc.addPanel(toParent: self, at: view.subviews.firstIndex(of: bottomToolView) ?? -1 , animated: false)
|
||||
|
||||
topBannerView.frame = .zero
|
||||
topBannerView.alpha = 0.0
|
||||
view.addSubview(topBannerView)
|
||||
topBannerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
topBannerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
|
||||
topBannerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
])
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .lightContent
|
||||
}
|
||||
|
||||
// MARK: FloatingPanelControllerDelegate
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
return FloatingPanelStocksLayout()
|
||||
}
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) {
|
||||
if vc.isAttracting == false {
|
||||
let loc = vc.surfaceLocation
|
||||
let minY = vc.surfaceLocation(for: .full).y
|
||||
let maxY = vc.surfaceLocation(for: .tip).y
|
||||
vc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY))
|
||||
}
|
||||
|
||||
if vc.surfaceLocation.y <= vc.surfaceLocation(for: .full).y + 100 {
|
||||
showStockTickerBanner()
|
||||
} else {
|
||||
hideStockTickerBanner()
|
||||
}
|
||||
}
|
||||
|
||||
private func showStockTickerBanner() {
|
||||
// Present top bar with dissolve animation
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.topBannerView.alpha = 1.0
|
||||
self.labelStackView.alpha = 0.0
|
||||
self.view.backgroundColor = .black
|
||||
}
|
||||
}
|
||||
|
||||
private func hideStockTickerBanner() {
|
||||
// Dimiss top bar with dissolve animation
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.topBannerView.alpha = 0.0
|
||||
self.labelStackView.alpha = 1.0
|
||||
self.view.backgroundColor = .black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NewsViewController: UIViewController {
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FloatingPanelLayout
|
||||
|
||||
class FloatingPanelStocksLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .tip
|
||||
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 56.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
/* Visible + ToolView */
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 85.0 + 44.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FloatingPanelBehavior
|
||||
|
||||
class FloatingPanelStocksBehavior: FloatingPanelBehavior {
|
||||
let springDecelerationRate: CGFloat = UIScrollView.DecelerationRate.fast.rawValue
|
||||
let springResponseTime: CGFloat = 0.25
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// Stocks
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/10/12.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
@IBOutlet var topBannerView: UIImageView!
|
||||
@IBOutlet weak var labelStackView: UIStackView!
|
||||
@IBOutlet weak var bottomToolView: UIView!
|
||||
|
||||
var fpc: FloatingPanelController!
|
||||
var newsVC: NewsViewController!
|
||||
|
||||
var initialColor: UIColor = .black
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
initialColor = view.backgroundColor!
|
||||
// Initialize FloatingPanelController
|
||||
fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
|
||||
// Initialize FloatingPanelController and add the view
|
||||
fpc.surfaceView.backgroundColor = UIColor(displayP3Red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0)
|
||||
fpc.surfaceView.cornerRadius = 24.0
|
||||
fpc.surfaceView.shadowHidden = true
|
||||
fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale
|
||||
fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2)
|
||||
|
||||
newsVC = storyboard?.instantiateViewController(withIdentifier: "News") as? NewsViewController
|
||||
|
||||
// Set a content view controller
|
||||
fpc.set(contentViewController: newsVC)
|
||||
fpc.track(scrollView: newsVC.scrollView)
|
||||
|
||||
fpc.addPanel(toParent: self, belowView: bottomToolView, animated: false)
|
||||
|
||||
topBannerView.frame = .zero
|
||||
topBannerView.alpha = 0.0
|
||||
view.addSubview(topBannerView)
|
||||
topBannerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
topBannerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
|
||||
topBannerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
])
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .lightContent
|
||||
}
|
||||
|
||||
// MARK: FloatingPanelControllerDelegate
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
return FloatingPanelStocksLayout()
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
|
||||
return FloatingPanelStocksBehavior()
|
||||
}
|
||||
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
|
||||
if vc.position == .full {
|
||||
// Dimiss top bar with dissolve animation
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.topBannerView.alpha = 0.0
|
||||
self.labelStackView.alpha = 1.0
|
||||
self.view.backgroundColor = self.initialColor
|
||||
}
|
||||
}
|
||||
}
|
||||
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {
|
||||
if targetPosition == .full {
|
||||
// Present top bar with dissolve animation
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.topBannerView.alpha = 1.0
|
||||
self.labelStackView.alpha = 0.0
|
||||
self.view.backgroundColor = .black
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NewsViewController: UIViewController {
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
}
|
||||
|
||||
|
||||
// MARK: My custom layout
|
||||
|
||||
class FloatingPanelStocksLayout: FloatingPanelLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .tip
|
||||
}
|
||||
|
||||
var topInteractionBuffer: CGFloat { return 0.0 }
|
||||
var bottomInteractionBuffer: CGFloat { return 0.0 }
|
||||
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return 56.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 85.0 + 44.0 // Visible + ToolView
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: My custom behavior
|
||||
|
||||
class FloatingPanelStocksBehavior: FloatingPanelBehavior {
|
||||
var velocityThreshold: CGFloat {
|
||||
return 15.0
|
||||
}
|
||||
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
let timing = timeingCurve(to: targetPosition, with: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
}
|
||||
|
||||
private func timeingCurve(to: FloatingPanelPosition, with velocity: CGVector) -> UITimingCurveProvider {
|
||||
let damping = self.damping(with: velocity)
|
||||
return UISpringTimingParameters(dampingRatio: damping,
|
||||
frequencyResponse: 0.4,
|
||||
initialVelocity: velocity)
|
||||
}
|
||||
|
||||
private func damping(with velocity: CGVector) -> CGFloat {
|
||||
switch velocity.dy {
|
||||
case ...(-velocityThreshold):
|
||||
return 0.7
|
||||
case velocityThreshold...:
|
||||
return 0.7
|
||||
default:
|
||||
return 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.5.5"
|
||||
s.version = "1.5.1"
|
||||
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
|
||||
s.description = <<-DESC
|
||||
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
The new interface displays the related contents and utilities in parallel as a user wants.
|
||||
DESC
|
||||
s.homepage = "https://github.com/SCENEE/FloatingPanel"
|
||||
s.author = "Shin Yamamoto"
|
||||
s.social_media_url = "https://twitter.com/scenee"
|
||||
# s.screenshots = ""
|
||||
|
||||
s.platform = :ios, "10.0"
|
||||
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => s.version.to_s }
|
||||
s.source_files = "Sources/*.swift"
|
||||
s.swift_versions = ['5.1', '5.2', '5.3']
|
||||
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => "v#{s.version}" }
|
||||
s.source_files = "Framework/Sources/*.swift"
|
||||
s.swift_version = "4.0"
|
||||
|
||||
s.framework = "UIKit"
|
||||
|
||||
s.author = { "Shin Yamamoto" => "shin@scenee.com" }
|
||||
s.license = { :type => "MIT", :file => "LICENSE" }
|
||||
s.social_media_url = "https://twitter.com/scenee"
|
||||
end
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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>FILEHEADER</key>
|
||||
<string> Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
+7
-9
@@ -4,15 +4,16 @@
|
||||
<FileRef
|
||||
location = "group:README.md">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:FloatingPanel.xcodeproj">
|
||||
</FileRef>
|
||||
<Group
|
||||
location = "group:Framework"
|
||||
name = "Framework">
|
||||
<FileRef
|
||||
location = "group:FloatingPanel.xcodeproj">
|
||||
</FileRef>
|
||||
</Group>
|
||||
<Group
|
||||
location = "group:Examples"
|
||||
name = "Examples">
|
||||
<FileRef
|
||||
location = "group:Maps-SwiftUI/Maps-SwiftUI.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Maps/Maps.xcodeproj">
|
||||
</FileRef>
|
||||
@@ -22,8 +23,5 @@
|
||||
<FileRef
|
||||
location = "group:Samples/Samples.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:SamplesObjC/SamplesObjC.xcodeproj">
|
||||
</FileRef>
|
||||
</Group>
|
||||
</Workspace>
|
||||
|
||||
+76
-132
@@ -7,34 +7,24 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542753C522C49A6E00D17955 /* LayoutTests.swift */; };
|
||||
54352E9621A51A2500CBCA08 /* Transitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* Transitioning.swift */; };
|
||||
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* PassthroughView.swift */; };
|
||||
5450EEE421646DF500135936 /* Behavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* Behavior.swift */; };
|
||||
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */; };
|
||||
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
|
||||
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
|
||||
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
|
||||
545DB9D02151169500CA77B8 /* ControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ControllerTests.swift */; };
|
||||
545DB9D02151169500CA77B8 /* FloatingPanelControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* FloatingPanelControllerTests.swift */; };
|
||||
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* Controller.swift */; };
|
||||
545DBA2B2152383100CA77B8 /* GrabberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberView.swift */; };
|
||||
546055BF2333C4740069F400 /* TestSupports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542753C722C49A8F00D17955 /* TestSupports.swift */; };
|
||||
5469F4A224B003EF00537F8A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */; };
|
||||
5469F4A324B003EF00537F8A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4A024B003EF00537F8A /* AppDelegate.swift */; };
|
||||
5469F4AE24B30D7E00537F8A /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AD24B30D7E00537F8A /* State.swift */; };
|
||||
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */; };
|
||||
5469F4B224B30F1100537F8A /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B124B30F1100537F8A /* Position.swift */; };
|
||||
5469F4B424B30F3500537F8A /* LayoutReferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutReferences.swift */; };
|
||||
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549C371E2361E15D007D8058 /* ExtensionTests.swift */; };
|
||||
549E944522CF295D0050AECF /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E944422CF295D0050AECF /* StateTests.swift */; };
|
||||
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* CoreTests.swift */; };
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */; };
|
||||
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DD215118C800CA77B8 /* UIExtensions.swift */; };
|
||||
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */; };
|
||||
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */; };
|
||||
54A6B6B122968B530077F348 /* FloatingPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* FloatingPanelTests.swift */; };
|
||||
54A6B6B622968F710077F348 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54A6B6B522968F710077F348 /* LaunchScreen.storyboard */; };
|
||||
54A6B6B82296A8520077F348 /* FloatingPanelViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* FloatingPanelViewTests.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
|
||||
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* SurfaceView.swift */; };
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* BackdropView.swift */; };
|
||||
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* Layout.swift */; };
|
||||
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* Core.swift */; };
|
||||
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DBA3DB262E938500D75969 /* Extensions.swift */; };
|
||||
54E3992727141F5100A8F9ED /* FloatingPanel.docc in Sources */ = {isa = PBXBuildFile; fileRef = 54E3992627141F5100A8F9ED /* FloatingPanel.docc */; };
|
||||
5D82A6B528D18464006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
54CDC5D3215B6D5A007D205C /* FloatingPanelSurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */; };
|
||||
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */; };
|
||||
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */; };
|
||||
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */; };
|
||||
54E740CD218AFD67005C1A34 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E740CC218AFD67005C1A34 /* AppDelegate.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -55,39 +45,29 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
542753C522C49A6E00D17955 /* LayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutTests.swift; sourceTree = "<group>"; };
|
||||
542753C722C49A8F00D17955 /* TestSupports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSupports.swift; sourceTree = "<group>"; };
|
||||
54352E9521A51A2500CBCA08 /* Transitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transitioning.swift; sourceTree = "<group>"; };
|
||||
54352E9721A521CA00CBCA08 /* PassthroughView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassthroughView.swift; sourceTree = "<group>"; };
|
||||
5450EEE321646DF500135936 /* Behavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Behavior.swift; sourceTree = "<group>"; };
|
||||
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTransitioning.swift; sourceTree = "<group>"; };
|
||||
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
|
||||
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
|
||||
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanel.h; sourceTree = "<group>"; };
|
||||
545DB9C52151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FloatingPanelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9CF2151169500CA77B8 /* ControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerTests.swift; sourceTree = "<group>"; };
|
||||
545DB9CF2151169500CA77B8 /* FloatingPanelControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelControllerTests.swift; sourceTree = "<group>"; };
|
||||
545DB9D12151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9DF21511AC100CA77B8 /* Controller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Controller.swift; sourceTree = "<group>"; };
|
||||
545DBA2A2152383100CA77B8 /* GrabberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabberView.swift; sourceTree = "<group>"; };
|
||||
5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
5469F4A024B003EF00537F8A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
5469F4A124B003EF00537F8A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5469F4AD24B30D7E00537F8A /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = "<group>"; };
|
||||
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAnchoring.swift; sourceTree = "<group>"; };
|
||||
5469F4B124B30F1100537F8A /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = "<group>"; };
|
||||
5469F4B324B30F3500537F8A /* LayoutReferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutReferences.swift; sourceTree = "<group>"; };
|
||||
549C371E2361E15D007D8058 /* ExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionTests.swift; sourceTree = "<group>"; };
|
||||
549E944422CF295D0050AECF /* StateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceViewTests.swift; sourceTree = "<group>"; };
|
||||
545DB9DD215118C800CA77B8 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
|
||||
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelController.swift; sourceTree = "<group>"; };
|
||||
545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabberHandleView.swift; sourceTree = "<group>"; };
|
||||
54A6B6B022968B530077F348 /* FloatingPanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B522968F710077F348 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
54A6B6B72296A8520077F348 /* FloatingPanelViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelViewTests.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
||||
54CDC5D4215B6D8D007D205C /* BackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackdropView.swift; sourceTree = "<group>"; };
|
||||
54CFBFC2215CD045006B5735 /* Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; };
|
||||
54CFBFC4215CD09C006B5735 /* Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = "<group>"; };
|
||||
54DBA3DB262E938500D75969 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||
54E3992627141F5100A8F9ED /* FloatingPanel.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = FloatingPanel.docc; sourceTree = "<group>"; };
|
||||
54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelSurfaceView.swift; sourceTree = "<group>"; };
|
||||
54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBackdropView.swift; sourceTree = "<group>"; };
|
||||
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelLayout.swift; sourceTree = "<group>"; };
|
||||
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanel.swift; sourceTree = "<group>"; };
|
||||
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FloatingPanelTesting.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
54E740CC218AFD67005C1A34 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
54E740D8218AFD6A005C1A34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -95,7 +75,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5D82A6B528D18464006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -122,8 +101,8 @@
|
||||
children = (
|
||||
545DB9C32151169500CA77B8 /* Sources */,
|
||||
545DB9CE2151169500CA77B8 /* Tests */,
|
||||
54E740CB218AFD67005C1A34 /* TestingApp */,
|
||||
545DB9C22151169500CA77B8 /* Products */,
|
||||
5D82A6B328D18460006A44BA /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -140,24 +119,19 @@
|
||||
545DB9C32151169500CA77B8 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DB9DF21511AC100CA77B8 /* Controller.swift */,
|
||||
5469F4AD24B30D7E00537F8A /* State.swift */,
|
||||
5469F4B124B30F1100537F8A /* Position.swift */,
|
||||
54CFBFC4215CD09C006B5735 /* Core.swift */,
|
||||
54CFBFC2215CD045006B5735 /* Layout.swift */,
|
||||
5469F4B324B30F3500537F8A /* LayoutReferences.swift */,
|
||||
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */,
|
||||
5450EEE321646DF500135936 /* Behavior.swift */,
|
||||
54352E9721A521CA00CBCA08 /* PassthroughView.swift */,
|
||||
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */,
|
||||
54CDC5D4215B6D8D007D205C /* BackdropView.swift */,
|
||||
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
|
||||
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
|
||||
54DBA3DB262E938500D75969 /* Extensions.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9C52151169500CA77B8 /* Info.plist */,
|
||||
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */,
|
||||
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */,
|
||||
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */,
|
||||
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */,
|
||||
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */,
|
||||
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */,
|
||||
54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */,
|
||||
54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */,
|
||||
545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */,
|
||||
545DB9DD215118C800CA77B8 /* UIExtensions.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -165,37 +139,24 @@
|
||||
545DB9CE2151169500CA77B8 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5469F49E24B003EF00537F8A /* TestingApp */,
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */,
|
||||
545DB9CF2151169500CA77B8 /* ControllerTests.swift */,
|
||||
542753C522C49A6E00D17955 /* LayoutTests.swift */,
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */,
|
||||
549E944422CF295D0050AECF /* StateTests.swift */,
|
||||
549C371E2361E15D007D8058 /* ExtensionTests.swift */,
|
||||
542753C722C49A8F00D17955 /* TestSupports.swift */,
|
||||
54A6B6B022968B530077F348 /* FloatingPanelTests.swift */,
|
||||
54A6B6B72296A8520077F348 /* FloatingPanelViewTests.swift */,
|
||||
545DB9CF2151169500CA77B8 /* FloatingPanelControllerTests.swift */,
|
||||
545DB9D12151169500CA77B8 /* Info.plist */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5469F49E24B003EF00537F8A /* TestingApp */ = {
|
||||
54E740CB218AFD67005C1A34 /* TestingApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */,
|
||||
5469F4A024B003EF00537F8A /* AppDelegate.swift */,
|
||||
5469F4A124B003EF00537F8A /* Info.plist */,
|
||||
54E740CC218AFD67005C1A34 /* AppDelegate.swift */,
|
||||
54E740D8218AFD6A005C1A34 /* Info.plist */,
|
||||
54A6B6B522968F710077F348 /* LaunchScreen.storyboard */,
|
||||
);
|
||||
path = TestingApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5D82A6B328D18460006A44BA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -271,12 +232,12 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1010;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = scenee;
|
||||
TargetAttributes = {
|
||||
545DB9C02151169500CA77B8 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
LastSwiftMigration = 1110;
|
||||
LastSwiftMigration = 1000;
|
||||
};
|
||||
545DB9C92151169500CA77B8 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
@@ -326,7 +287,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5469F4A224B003EF00537F8A /* LaunchScreen.storyboard in Resources */,
|
||||
54A6B6B622968F710077F348 /* LaunchScreen.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -337,22 +298,17 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5469F4B224B30F1100537F8A /* Position.swift in Sources */,
|
||||
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */,
|
||||
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */,
|
||||
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */,
|
||||
5469F4B424B30F3500537F8A /* LayoutReferences.swift in Sources */,
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */,
|
||||
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */,
|
||||
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */,
|
||||
54CDC5D3215B6D5A007D205C /* FloatingPanelSurfaceView.swift in Sources */,
|
||||
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */,
|
||||
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */,
|
||||
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */,
|
||||
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
|
||||
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */,
|
||||
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
|
||||
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
|
||||
545DBA2B2152383100CA77B8 /* GrabberView.swift in Sources */,
|
||||
54E3992727141F5100A8F9ED /* FloatingPanel.docc in Sources */,
|
||||
54352E9621A51A2500CBCA08 /* Transitioning.swift in Sources */,
|
||||
5469F4AE24B30D7E00537F8A /* State.swift in Sources */,
|
||||
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */,
|
||||
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */,
|
||||
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */,
|
||||
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */,
|
||||
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -360,13 +316,9 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */,
|
||||
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */,
|
||||
545DB9D02151169500CA77B8 /* ControllerTests.swift in Sources */,
|
||||
549E944522CF295D0050AECF /* StateTests.swift in Sources */,
|
||||
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */,
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */,
|
||||
546055BF2333C4740069F400 /* TestSupports.swift in Sources */,
|
||||
54A6B6B122968B530077F348 /* FloatingPanelTests.swift in Sources */,
|
||||
545DB9D02151169500CA77B8 /* FloatingPanelControllerTests.swift in Sources */,
|
||||
54A6B6B82296A8520077F348 /* FloatingPanelViewTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -374,7 +326,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5469F4A324B003EF00537F8A /* AppDelegate.swift in Sources */,
|
||||
54E740CD218AFD67005C1A34 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -420,7 +372,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -454,7 +405,6 @@
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -486,7 +436,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -513,7 +462,6 @@
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -524,7 +472,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -546,7 +493,7 @@
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -555,7 +502,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -575,7 +521,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
@@ -627,7 +573,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
|
||||
INFOPLIST_FILE = TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -645,7 +591,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
|
||||
INFOPLIST_FILE = TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -684,7 +630,6 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -718,7 +663,6 @@
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -746,10 +690,10 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG __FP_LOG";
|
||||
SWIFT_COMPILATION_MODE = singlefile;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG __FP_LOG";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Test;
|
||||
@@ -779,7 +723,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
|
||||
INFOPLIST_FILE = TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
|
||||
+1
-1
@@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:SamplesObjC.xcodeproj">
|
||||
location = "self:FloatingModalController.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+15
-13
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -27,20 +27,9 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545DB9C02151169500CA77B8"
|
||||
BuildableName = "FloatingPanel.framework"
|
||||
BlueprintName = "FloatingPanel"
|
||||
ReferencedContainer = "container:FloatingPanel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545DB9C92151169500CA77B8"
|
||||
@@ -50,6 +39,17 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545DB9C02151169500CA77B8"
|
||||
BuildableName = "FloatingPanel.framework"
|
||||
BlueprintName = "FloatingPanel"
|
||||
ReferencedContainer = "container:FloatingPanel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -70,6 +70,8 @@
|
||||
ReferencedContainer = "container:FloatingPanel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
FOUNDATION_EXPORT double FloatingPanelVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char FloatingPanelVersionString[];
|
||||
@@ -0,0 +1,946 @@
|
||||
//
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import UIKit.UIGestureRecognizerSubclass // For Xcode 9.4.1
|
||||
|
||||
///
|
||||
/// FloatingPanel presentation model
|
||||
///
|
||||
class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate {
|
||||
// MUST be a weak reference to prevent UI freeze on the presentation modally
|
||||
weak var viewcontroller: FloatingPanelController!
|
||||
|
||||
let surfaceView: FloatingPanelSurfaceView
|
||||
let backdropView: FloatingPanelBackdropView
|
||||
var layoutAdapter: FloatingPanelLayoutAdapter
|
||||
var behavior: FloatingPanelBehavior
|
||||
|
||||
weak var scrollView: UIScrollView? {
|
||||
didSet {
|
||||
guard let scrollView = scrollView else { return }
|
||||
scrollView.panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var state: FloatingPanelPosition = .hidden {
|
||||
didSet { viewcontroller.delegate?.floatingPanelDidChangePosition(viewcontroller) }
|
||||
}
|
||||
|
||||
private var isBottomState: Bool {
|
||||
let remains = layoutAdapter.supportedPositions.filter { $0.rawValue > state.rawValue }
|
||||
return remains.count == 0
|
||||
}
|
||||
|
||||
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
|
||||
var isRemovalInteractionEnabled: Bool = false
|
||||
|
||||
fileprivate var animator: UIViewPropertyAnimator? {
|
||||
didSet {
|
||||
// This intends to avoid `tableView(_:didSelectRowAt:)` not being
|
||||
// called on first tap after the moving animation, but it doesn't
|
||||
// seem to be enough. The same issue happens on Apple Maps so it
|
||||
// might be an issue in `UITableView`.
|
||||
scrollView?.isUserInteractionEnabled = (animator == nil)
|
||||
}
|
||||
}
|
||||
|
||||
private var initialFrame: CGRect = .zero
|
||||
private var initialTranslationY: CGFloat = 0
|
||||
private var initialLocation: CGPoint = .nan
|
||||
|
||||
var interactionInProgress: Bool = false
|
||||
var isDecelerating: Bool = false
|
||||
|
||||
// Scroll handling
|
||||
private var initialScrollOffset: CGPoint = .zero
|
||||
private var initialScrollFrame: CGRect = .zero
|
||||
private var stopScrollDeceleration: Bool = false
|
||||
private var scrollBouncable = false
|
||||
private var scrollIndictorVisible = false
|
||||
|
||||
private var isScrollLocked: Bool = false
|
||||
|
||||
// MARK: - Interface
|
||||
|
||||
init(_ vc: FloatingPanelController, layout: FloatingPanelLayout, behavior: FloatingPanelBehavior) {
|
||||
viewcontroller = vc
|
||||
|
||||
surfaceView = FloatingPanelSurfaceView()
|
||||
|
||||
backdropView = FloatingPanelBackdropView()
|
||||
backdropView.alpha = 0.0
|
||||
|
||||
self.layoutAdapter = FloatingPanelLayoutAdapter(surfaceView: surfaceView,
|
||||
backdropView: backdropView,
|
||||
layout: layout)
|
||||
self.behavior = behavior
|
||||
|
||||
panGestureRecognizer = FloatingPanelPanGestureRecognizer()
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
panGestureRecognizer.name = "FloatingPanelSurface"
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
panGestureRecognizer.floatingPanel = self
|
||||
|
||||
surfaceView.addGestureRecognizer(panGestureRecognizer)
|
||||
panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
|
||||
panGestureRecognizer.delegate = self
|
||||
}
|
||||
|
||||
func move(to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
move(from: state, to: to, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
private func move(from: FloatingPanelPosition, to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
if state != layoutAdapter.topMostState {
|
||||
lockScrollView()
|
||||
}
|
||||
tearDownActiveInteraction()
|
||||
|
||||
if animated {
|
||||
let animator: UIViewPropertyAnimator
|
||||
switch (from, to) {
|
||||
case (.hidden, let to):
|
||||
animator = behavior.addAnimator(self.viewcontroller, to: to)
|
||||
case (let from, .hidden):
|
||||
animator = behavior.removeAnimator(self.viewcontroller, from: from)
|
||||
case (let from, let to):
|
||||
animator = behavior.moveAnimator(self.viewcontroller, from: from, to: to)
|
||||
}
|
||||
|
||||
animator.addAnimations { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
|
||||
self.state = to
|
||||
self.updateLayout(to: to)
|
||||
}
|
||||
animator.addCompletion { [weak self] _ in
|
||||
guard let `self` = self else { return }
|
||||
self.animator = nil
|
||||
self.unlockScrollView()
|
||||
completion?()
|
||||
}
|
||||
self.animator = animator
|
||||
animator.startAnimation()
|
||||
} else {
|
||||
self.state = to
|
||||
self.updateLayout(to: to)
|
||||
self.unlockScrollView()
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Layout update
|
||||
|
||||
private func updateLayout(to target: FloatingPanelPosition) {
|
||||
self.layoutAdapter.activateLayout(of: target)
|
||||
}
|
||||
|
||||
private func getBackdropAlpha(with translation: CGPoint) -> CGFloat {
|
||||
let currentY = surfaceView.frame.minY
|
||||
|
||||
let next = directionalPosition(at: currentY, with: translation)
|
||||
let pre = redirectionalPosition(at: currentY, with: translation)
|
||||
let nextY = layoutAdapter.positionY(for: next)
|
||||
let preY = layoutAdapter.positionY(for: pre)
|
||||
|
||||
let nextAlpha = layoutAdapter.layout.backdropAlphaFor(position: next)
|
||||
let preAlpha = layoutAdapter.layout.backdropAlphaFor(position: pre)
|
||||
|
||||
if preY == nextY {
|
||||
return preAlpha
|
||||
} else {
|
||||
return preAlpha + max(min(1.0, 1.0 - (nextY - currentY) / (nextY - preY) ), 0.0) * (nextAlpha - preAlpha)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizerDelegate
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
|
||||
/* log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */
|
||||
|
||||
if viewcontroller.delegate?.floatingPanel(viewcontroller, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false {
|
||||
return true
|
||||
}
|
||||
|
||||
switch otherGestureRecognizer {
|
||||
case is UIPanGestureRecognizer,
|
||||
is UISwipeGestureRecognizer,
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
// all gestures of the tracking scroll view should be recognized in parallel
|
||||
// and handle them in self.handle(panGesture:)
|
||||
return scrollView?.gestureRecognizers?.contains(otherGestureRecognizer) ?? false
|
||||
default:
|
||||
// Should recognize tap/long press gestures in parallel when the surface view is at an anchor position.
|
||||
let surfaceFrame = surfaceView.layer.presentation()?.frame ?? surfaceView.frame
|
||||
return surfaceFrame.minY == layoutAdapter.positionY(for: state)
|
||||
}
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
/* log.debug("shouldBeRequiredToFailBy", otherGestureRecognizer) */
|
||||
return false
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
|
||||
/* log.debug("shouldRequireFailureOf", otherGestureRecognizer) */
|
||||
|
||||
// Should begin the pan gesture without waiting for the tracking scroll view's gestures.
|
||||
// `scrollView.gestureRecognizers` can contains the following gestures
|
||||
// * UIScrollViewDelayedTouchesBeganGestureRecognizer
|
||||
// * UIScrollViewPanGestureRecognizer (scrollView.panGestureRecognizer)
|
||||
// * _UIDragAutoScrollGestureRecognizer
|
||||
// * _UISwipeActionPanGestureRecognizer
|
||||
// * UISwipeDismissalGestureRecognizer
|
||||
if let scrollView = scrollView {
|
||||
// On short contents scroll, `_UISwipeActionPanGestureRecognizer` blocks
|
||||
// the panel's pan gesture if not returns false
|
||||
if let scrollGestureRecognizers = scrollView.gestureRecognizers,
|
||||
scrollGestureRecognizers.contains(otherGestureRecognizer) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if viewcontroller.delegate?.floatingPanel(viewcontroller, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
switch otherGestureRecognizer {
|
||||
case is UIPanGestureRecognizer,
|
||||
is UISwipeGestureRecognizer,
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
// Do not begin the pan gesture until these gestures fail
|
||||
return true
|
||||
default:
|
||||
// Should begin the pan gesture without waiting tap/long press gestures fail
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var grabberAreaFrame: CGRect {
|
||||
let grabberAreaFrame = CGRect(x: surfaceView.bounds.origin.x,
|
||||
y: surfaceView.bounds.origin.y,
|
||||
width: surfaceView.bounds.width,
|
||||
height: surfaceView.topGrabberBarHeight * 2)
|
||||
return grabberAreaFrame
|
||||
}
|
||||
|
||||
// MARK: - Gesture handling
|
||||
@objc func handle(panGesture: UIPanGestureRecognizer) {
|
||||
let velocity = panGesture.velocity(in: panGesture.view)
|
||||
|
||||
switch panGesture {
|
||||
case scrollView?.panGestureRecognizer:
|
||||
guard let scrollView = scrollView else { return }
|
||||
|
||||
let location = panGesture.location(in: surfaceView)
|
||||
|
||||
let belowTop = surfaceView.frame.minY > layoutAdapter.topY
|
||||
|
||||
log.debug("scroll gesture(\(state):\(panGesture.state)) --",
|
||||
"belowTop = \(belowTop),",
|
||||
"interactionInProgress = \(interactionInProgress),",
|
||||
"scroll offset = \(scrollView.contentOffset.y),",
|
||||
"location = \(location.y), velocity = \(velocity.y)")
|
||||
|
||||
if belowTop {
|
||||
// Scroll offset pinning
|
||||
if state == layoutAdapter.topMostState {
|
||||
if interactionInProgress {
|
||||
log.debug("settle offset --", initialScrollOffset.y)
|
||||
scrollView.setContentOffset(initialScrollOffset, animated: false)
|
||||
} else {
|
||||
if grabberAreaFrame.contains(location) {
|
||||
// Preserve the current content offset in moving from full.
|
||||
scrollView.setContentOffset(initialScrollOffset, animated: false)
|
||||
} else {
|
||||
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
|
||||
if offset < 0 {
|
||||
fitToBounds(scrollView: scrollView)
|
||||
let translation = panGesture.translation(in: panGestureRecognizer.view!.superview)
|
||||
startInteraction(with: translation, at: location)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scrollView.setContentOffset(initialScrollOffset, animated: false)
|
||||
}
|
||||
|
||||
// Always hide a scroll indicator at the non-top.
|
||||
if interactionInProgress {
|
||||
lockScrollView()
|
||||
}
|
||||
} else {
|
||||
// Always show a scroll indicator at the top.
|
||||
if interactionInProgress {
|
||||
unlockScrollView()
|
||||
} else {
|
||||
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
|
||||
if state == layoutAdapter.topMostState, offset < 0, velocity.y > 0 {
|
||||
fitToBounds(scrollView: scrollView)
|
||||
let translation = panGesture.translation(in: panGestureRecognizer.view!.superview)
|
||||
startInteraction(with: translation, at: location)
|
||||
}
|
||||
}
|
||||
}
|
||||
case panGestureRecognizer:
|
||||
let translation = panGesture.translation(in: panGestureRecognizer.view!.superview)
|
||||
let location = panGesture.location(in: panGesture.view)
|
||||
|
||||
log.debug("panel gesture(\(state):\(panGesture.state)) --",
|
||||
"translation = \(translation.y), location = \(location.y), velocity = \(velocity.y)")
|
||||
|
||||
if let animator = self.animator {
|
||||
log.debug("panel animation interrupted!!!")
|
||||
if animator.isInterruptible {
|
||||
animator.stopAnimation(false)
|
||||
animator.finishAnimation(at: .current)
|
||||
}
|
||||
self.animator = nil
|
||||
|
||||
// A user can stop a panel at the nearest Y of a target position
|
||||
if abs(surfaceView.frame.minY - layoutAdapter.topY) < 1.0 {
|
||||
surfaceView.frame.origin.y = layoutAdapter.topY
|
||||
}
|
||||
}
|
||||
|
||||
if interactionInProgress == false,
|
||||
viewcontroller.delegate?.floatingPanelShouldBeginDragging(viewcontroller) == false {
|
||||
return
|
||||
}
|
||||
|
||||
if panGesture.state == .began {
|
||||
panningBegan(at: location)
|
||||
return
|
||||
}
|
||||
|
||||
if shouldScrollViewHandleTouch(scrollView, point: location, velocity: velocity) {
|
||||
return
|
||||
}
|
||||
|
||||
switch panGesture.state {
|
||||
case .changed:
|
||||
if interactionInProgress == false {
|
||||
startInteraction(with: translation, at: location)
|
||||
}
|
||||
panningChange(with: translation)
|
||||
case .ended, .cancelled, .failed:
|
||||
panningEnd(with: translation, velocity: velocity)
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldScrollViewHandleTouch(_ scrollView: UIScrollView?, point: CGPoint, velocity: CGPoint) -> Bool {
|
||||
// When no scrollView, nothing to handle.
|
||||
guard let scrollView = scrollView else { return false }
|
||||
|
||||
// For _UISwipeActionPanGestureRecognizer
|
||||
if let scrollGestureRecognizers = scrollView.gestureRecognizers {
|
||||
for gesture in scrollGestureRecognizers {
|
||||
guard gesture.state == .began || gesture.state == .changed
|
||||
else { continue }
|
||||
|
||||
if gesture != scrollView.panGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard
|
||||
state == layoutAdapter.topMostState, // When not top most(i.e. .full), don't scroll.
|
||||
interactionInProgress == false, // When interaction already in progress, don't scroll.
|
||||
surfaceView.frame.minY == layoutAdapter.topY
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
// When the current and initial point within grabber area, do scroll.
|
||||
if grabberAreaFrame.contains(point), !grabberAreaFrame.contains(initialLocation) {
|
||||
return true
|
||||
}
|
||||
|
||||
guard
|
||||
scrollView.frame.contains(initialLocation), // When initialLocation not in scrollView, don't scroll.
|
||||
!grabberAreaFrame.contains(point) // When point within grabber area, don't scroll.
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
|
||||
// The zero offset must be excluded because the offset is usually zero
|
||||
// after a panel moves from half/tip to full.
|
||||
if offset > 0.0 {
|
||||
return true
|
||||
}
|
||||
if scrollView.isDecelerating {
|
||||
return true
|
||||
}
|
||||
if velocity.y <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func panningBegan(at location: CGPoint) {
|
||||
// A user interaction does not always start from Began state of the pan gesture
|
||||
// because it can be recognized in scrolling a content in a content view controller.
|
||||
// So here just preserve the current state if needed.
|
||||
log.debug("panningBegan -- location = \(location.y)")
|
||||
initialLocation = location
|
||||
if state == layoutAdapter.topMostState {
|
||||
if let scrollView = scrollView {
|
||||
initialScrollFrame = scrollView.frame
|
||||
}
|
||||
} else {
|
||||
if let scrollView = scrollView {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func panningChange(with translation: CGPoint) {
|
||||
log.debug("panningChange -- translation = \(translation.y)")
|
||||
let pre = surfaceView.frame.minY
|
||||
let dy = translation.y - initialTranslationY
|
||||
|
||||
layoutAdapter.updateInteractiveTopConstraint(diff: dy,
|
||||
allowsTopBuffer: allowsTopBuffer(for: dy),
|
||||
with: behavior)
|
||||
|
||||
backdropView.alpha = getBackdropAlpha(with: translation)
|
||||
preserveContentVCLayoutIfNeeded()
|
||||
|
||||
let didMove = (pre != surfaceView.frame.minY)
|
||||
guard didMove else { return }
|
||||
|
||||
viewcontroller.delegate?.floatingPanelDidMove(viewcontroller)
|
||||
}
|
||||
|
||||
private func allowsTopBuffer(for translationY: CGFloat) -> Bool {
|
||||
let preY = surfaceView.frame.minY
|
||||
let nextY = initialFrame.offsetBy(dx: 0.0, dy: translationY).minY
|
||||
if let scrollView = scrollView, scrollView.panGestureRecognizer.state == .changed,
|
||||
preY > 0 && preY > nextY {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private var disabledBottomAutoLayout = false
|
||||
// Prevent stretching a view having a constraint to SafeArea.bottom in an overflow
|
||||
// from the full position because SafeArea is global in a screen.
|
||||
private func preserveContentVCLayoutIfNeeded() {
|
||||
// Must include topY
|
||||
if (surfaceView.frame.minY <= layoutAdapter.topY) {
|
||||
if !disabledBottomAutoLayout {
|
||||
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
|
||||
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
|
||||
case const.firstAnchor:
|
||||
(const.secondItem as? UIView)?.disableAutoLayout()
|
||||
const.isActive = false
|
||||
case const.secondAnchor:
|
||||
(const.firstItem as? UIView)?.disableAutoLayout()
|
||||
const.isActive = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
disabledBottomAutoLayout = true
|
||||
} else {
|
||||
if disabledBottomAutoLayout {
|
||||
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
|
||||
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
|
||||
case const.firstAnchor:
|
||||
(const.secondItem as? UIView)?.enableAutoLayout()
|
||||
const.isActive = true
|
||||
case const.secondAnchor:
|
||||
(const.firstItem as? UIView)?.enableAutoLayout()
|
||||
const.isActive = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
disabledBottomAutoLayout = false
|
||||
}
|
||||
}
|
||||
|
||||
private func panningEnd(with translation: CGPoint, velocity: CGPoint) {
|
||||
log.debug("panningEnd -- translation = \(translation.y), velocity = \(velocity.y)")
|
||||
|
||||
if state == .hidden {
|
||||
log.debug("Already hidden")
|
||||
return
|
||||
}
|
||||
|
||||
stopScrollDeceleration = (surfaceView.frame.minY > layoutAdapter.topY) // Projecting the dragging to the scroll dragging or not
|
||||
if stopScrollDeceleration {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.stopScrollingWithDeceleration(at: self.initialScrollOffset)
|
||||
}
|
||||
}
|
||||
|
||||
let targetPosition = self.targetPosition(with: velocity)
|
||||
let distance = self.distance(to: targetPosition)
|
||||
|
||||
endInteraction(for: targetPosition)
|
||||
|
||||
if isRemovalInteractionEnabled, isBottomState {
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0,
|
||||
dy: min(abs(velocity.y)/distance, behavior.removalVelocity)) : .zero
|
||||
|
||||
if shouldStartRemovalAnimation(with: velocityVector) {
|
||||
|
||||
viewcontroller.delegate?.floatingPanelDidEndDraggingToRemove(viewcontroller, withVelocity: velocity)
|
||||
self.startRemovalAnimation(with: velocityVector) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.viewcontroller.dismiss(animated: false, completion: { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.viewcontroller.delegate?.floatingPanelDidEndRemove(self.viewcontroller)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewcontroller.delegate?.floatingPanelDidEndDragging(viewcontroller, withVelocity: velocity, targetPosition: targetPosition)
|
||||
|
||||
// Workaround: Disable a tracking scroll to prevent bouncing a scroll content in a panel animating
|
||||
let isScrollEnabled = scrollView?.isScrollEnabled
|
||||
if let scrollView = scrollView, targetPosition != .full {
|
||||
scrollView.isScrollEnabled = false
|
||||
}
|
||||
|
||||
startAnimation(to: targetPosition, at: distance, with: velocity)
|
||||
|
||||
// Workaround: Reset `self.scrollView.isScrollEnabled`
|
||||
if let scrollView = scrollView, targetPosition != .full,
|
||||
let isScrollEnabled = isScrollEnabled {
|
||||
scrollView.isScrollEnabled = isScrollEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldStartRemovalAnimation(with velocityVector: CGVector) -> Bool {
|
||||
let posY = layoutAdapter.positionY(for: state)
|
||||
let currentY = surfaceView.frame.minY
|
||||
let bottomMaxY = layoutAdapter.bottomMaxY
|
||||
let vth = behavior.removalVelocity
|
||||
let pth = max(min(behavior.removalProgress, 1.0), 0.0)
|
||||
|
||||
let num = (currentY - posY)
|
||||
let den = (bottomMaxY - posY)
|
||||
|
||||
guard num >= 0, den != 0, (num / den >= pth || velocityVector.dy == vth)
|
||||
else { return false }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func startRemovalAnimation(with velocityVector: CGVector, completion: (() -> Void)?) {
|
||||
let animator = self.behavior.removalInteractionAnimator(self.viewcontroller, with: velocityVector)
|
||||
|
||||
animator.addAnimations { [weak self] in
|
||||
self?.updateLayout(to: .hidden)
|
||||
}
|
||||
animator.addCompletion({ _ in
|
||||
self.animator = nil
|
||||
completion?()
|
||||
})
|
||||
self.animator = animator
|
||||
animator.startAnimation()
|
||||
}
|
||||
|
||||
private func startInteraction(with translation: CGPoint, at location: CGPoint) {
|
||||
/* Don't lock a scroll view to show a scroll indicator after hitting the top */
|
||||
log.debug("startInteraction -- translation = \(translation.y), location = \(location.y)")
|
||||
guard interactionInProgress == false else { return }
|
||||
|
||||
initialFrame = surfaceView.frame
|
||||
if state == layoutAdapter.topMostState, let scrollView = scrollView {
|
||||
if grabberAreaFrame.contains(location) {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
} else {
|
||||
settle(scrollView: scrollView)
|
||||
initialScrollOffset = scrollView.contentOffsetZero
|
||||
}
|
||||
log.debug("initial scroll offset --", initialScrollOffset)
|
||||
}
|
||||
|
||||
initialTranslationY = translation.y
|
||||
|
||||
viewcontroller.delegate?.floatingPanelWillBeginDragging(viewcontroller)
|
||||
|
||||
layoutAdapter.startInteraction(at: state)
|
||||
|
||||
interactionInProgress = true
|
||||
}
|
||||
|
||||
private func endInteraction(for targetPosition: FloatingPanelPosition) {
|
||||
log.debug("endInteraction to \(targetPosition)")
|
||||
|
||||
if let scrollView = scrollView {
|
||||
log.debug("endInteraction -- scroll offset = \(scrollView.contentOffset)")
|
||||
}
|
||||
|
||||
interactionInProgress = false
|
||||
|
||||
// Prevent to keep a scroll view indicator visible at the half/tip position
|
||||
if state != layoutAdapter.topMostState {
|
||||
lockScrollView()
|
||||
}
|
||||
|
||||
layoutAdapter.endInteraction(at: targetPosition)
|
||||
}
|
||||
|
||||
private func tearDownActiveInteraction() {
|
||||
// Cancel the pan gesture so that panningEnd(with:velocity:) is called
|
||||
panGestureRecognizer.isEnabled = false
|
||||
panGestureRecognizer.isEnabled = true
|
||||
}
|
||||
|
||||
private func startAnimation(to targetPosition: FloatingPanelPosition, at distance: CGFloat, with velocity: CGPoint) {
|
||||
log.debug("startAnimation to \(targetPosition) -- distance = \(distance), velocity = \(velocity.y)")
|
||||
|
||||
isDecelerating = true
|
||||
viewcontroller.delegate?.floatingPanelWillBeginDecelerating(viewcontroller)
|
||||
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: min(abs(velocity.y)/distance, 30.0)) : .zero
|
||||
let animator = behavior.interactionAnimator(self.viewcontroller, to: targetPosition, with: velocityVector)
|
||||
animator.addAnimations { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.state = targetPosition
|
||||
self.updateLayout(to: targetPosition)
|
||||
}
|
||||
animator.addCompletion { [weak self] pos in
|
||||
guard let `self` = self else { return }
|
||||
self.finishAnimation(at: targetPosition)
|
||||
}
|
||||
self.animator = animator
|
||||
animator.startAnimation()
|
||||
}
|
||||
|
||||
private func finishAnimation(at targetPosition: FloatingPanelPosition) {
|
||||
log.debug("finishAnimation to \(targetPosition)")
|
||||
|
||||
self.isDecelerating = false
|
||||
self.animator = nil
|
||||
|
||||
self.viewcontroller.delegate?.floatingPanelDidEndDecelerating(self.viewcontroller)
|
||||
|
||||
if let scrollView = scrollView {
|
||||
log.debug("finishAnimation -- scroll offset = \(scrollView.contentOffset)")
|
||||
}
|
||||
|
||||
stopScrollDeceleration = false
|
||||
// Don't unlock scroll view in animating view when presentation layer != model layer
|
||||
if state == layoutAdapter.topMostState {
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
|
||||
private func distance(to targetPosition: FloatingPanelPosition) -> CGFloat {
|
||||
let topY = layoutAdapter.topY
|
||||
let middleY = layoutAdapter.middleY
|
||||
let bottomY = layoutAdapter.bottomY
|
||||
let currentY = surfaceView.frame.minY
|
||||
|
||||
switch targetPosition {
|
||||
case .full:
|
||||
return CGFloat(abs(currentY - topY))
|
||||
case .half:
|
||||
return CGFloat(abs(currentY - middleY))
|
||||
case .tip:
|
||||
return CGFloat(abs(currentY - bottomY))
|
||||
case .hidden:
|
||||
fatalError("Now .hidden must not be used for a user interaction")
|
||||
}
|
||||
}
|
||||
|
||||
private func directionalPosition(at currentY: CGFloat, with translation: CGPoint) -> FloatingPanelPosition {
|
||||
return getPosition(at: currentY, with: translation, directional: true)
|
||||
}
|
||||
|
||||
private func redirectionalPosition(at currentY: CGFloat, with translation: CGPoint) -> FloatingPanelPosition {
|
||||
return getPosition(at: currentY, with: translation, directional: false)
|
||||
}
|
||||
|
||||
private func getPosition(at currentY: CGFloat, with translation: CGPoint, directional: Bool) -> FloatingPanelPosition {
|
||||
let supportedPositions: Set = layoutAdapter.supportedPositions
|
||||
if supportedPositions.count == 1 {
|
||||
return state
|
||||
}
|
||||
let isForwardYAxis = (translation.y >= 0)
|
||||
switch supportedPositions {
|
||||
case [.full, .half]:
|
||||
return (isForwardYAxis == directional) ? .half : .full
|
||||
case [.half, .tip]:
|
||||
return (isForwardYAxis == directional) ? .tip : .half
|
||||
case [.full, .tip]:
|
||||
return (isForwardYAxis == directional) ? .tip : .full
|
||||
default:
|
||||
let middleY = layoutAdapter.middleY
|
||||
if currentY > middleY {
|
||||
return (isForwardYAxis == directional) ? .tip : .half
|
||||
} else {
|
||||
return (isForwardYAxis == directional) ? .half : .full
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Distance travelled after decelerating to zero velocity at a constant rate.
|
||||
// Refer to the slides p176 of [Designing Fluid Interfaces](https://developer.apple.com/videos/play/wwdc2018/803/)
|
||||
private func project(initialVelocity: CGFloat, decelerationRate: CGFloat) -> CGFloat {
|
||||
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate)
|
||||
}
|
||||
|
||||
private func targetPosition(with velocity: CGPoint) -> (FloatingPanelPosition) {
|
||||
let currentY = surfaceView.frame.minY
|
||||
let supportedPositions = layoutAdapter.supportedPositions
|
||||
|
||||
if supportedPositions.count == 1 {
|
||||
return state
|
||||
}
|
||||
|
||||
switch supportedPositions {
|
||||
case [.full, .half]:
|
||||
return targetPosition(from: [.full, .half], at: currentY, velocity: velocity)
|
||||
case [.half, .tip]:
|
||||
return targetPosition(from: [.half, .tip], at: currentY, velocity: velocity)
|
||||
case [.full, .tip]:
|
||||
return targetPosition(from: [.full, .tip], at: currentY, velocity: velocity)
|
||||
default:
|
||||
/*
|
||||
[topY|full]---[th1]---[middleY|half]---[th2]---[bottomY|tip]
|
||||
*/
|
||||
let topY = layoutAdapter.topY
|
||||
let middleY = layoutAdapter.middleY
|
||||
let bottomY = layoutAdapter.bottomY
|
||||
|
||||
let nextState: FloatingPanelPosition
|
||||
let forwardYDirection: Bool
|
||||
|
||||
/*
|
||||
full <-> half <-> tip
|
||||
*/
|
||||
switch state {
|
||||
case .full:
|
||||
nextState = .half
|
||||
forwardYDirection = true
|
||||
case .half:
|
||||
nextState = (currentY > middleY) ? .tip : .full
|
||||
forwardYDirection = (currentY > middleY)
|
||||
case .tip:
|
||||
nextState = .half
|
||||
forwardYDirection = false
|
||||
case .hidden:
|
||||
fatalError("Now .hidden must not be used for a user interaction")
|
||||
}
|
||||
|
||||
let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: nextState), 1.0), 0.0)
|
||||
|
||||
let th1: CGFloat
|
||||
let th2: CGFloat
|
||||
|
||||
if forwardYDirection {
|
||||
th1 = topY + (middleY - topY) * redirectionalProgress
|
||||
th2 = middleY + (bottomY - middleY) * redirectionalProgress
|
||||
} else {
|
||||
th1 = middleY - (middleY - topY) * redirectionalProgress
|
||||
th2 = bottomY - (bottomY - middleY) * redirectionalProgress
|
||||
}
|
||||
|
||||
let decelerationRate = behavior.momentumProjectionRate(viewcontroller)
|
||||
|
||||
let baseY = abs(bottomY - topY)
|
||||
let vecY = velocity.y / baseY
|
||||
let pY = project(initialVelocity: vecY, decelerationRate: decelerationRate) * baseY + currentY
|
||||
|
||||
switch currentY {
|
||||
case ..<th1:
|
||||
switch pY {
|
||||
case bottomY...:
|
||||
return behavior.shouldProjectMomentum(viewcontroller, for: .tip) ? .tip : .half
|
||||
case middleY...:
|
||||
return .half
|
||||
case topY...:
|
||||
return .full
|
||||
default:
|
||||
return .full
|
||||
}
|
||||
case ...middleY:
|
||||
switch pY {
|
||||
case bottomY...:
|
||||
return behavior.shouldProjectMomentum(viewcontroller, for: .tip) ? .tip : .half
|
||||
case middleY...:
|
||||
return .half
|
||||
case topY...:
|
||||
return .half
|
||||
default:
|
||||
return .full
|
||||
}
|
||||
case ..<th2:
|
||||
switch pY {
|
||||
case bottomY...:
|
||||
return .tip
|
||||
case middleY...:
|
||||
return .half
|
||||
case topY...:
|
||||
return .half
|
||||
default:
|
||||
return behavior.shouldProjectMomentum(viewcontroller, for: .full) ? .full : .half
|
||||
}
|
||||
default:
|
||||
switch pY {
|
||||
case bottomY...:
|
||||
return .tip
|
||||
case middleY...:
|
||||
return .tip
|
||||
case topY...:
|
||||
return .half
|
||||
default:
|
||||
return behavior.shouldProjectMomentum(viewcontroller, for: .full) ? .full : .half
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func targetPosition(from positions: [FloatingPanelPosition], at currentY: CGFloat, velocity: CGPoint) -> FloatingPanelPosition {
|
||||
assert(positions.count == 2)
|
||||
|
||||
let top = positions[0]
|
||||
let bottom = positions[1]
|
||||
|
||||
let topY = layoutAdapter.positionY(for: top)
|
||||
let bottomY = layoutAdapter.positionY(for: bottom)
|
||||
|
||||
let target = top == state ? bottom : top
|
||||
let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: target), 1.0), 0.0)
|
||||
|
||||
let th = topY + (bottomY - topY) * redirectionalProgress
|
||||
|
||||
let decelerationRate = behavior.momentumProjectionRate(viewcontroller)
|
||||
let pY = project(initialVelocity: velocity.y, decelerationRate: decelerationRate) + currentY
|
||||
|
||||
switch currentY {
|
||||
case ..<th:
|
||||
if pY >= bottomY {
|
||||
return bottom
|
||||
} else {
|
||||
return top
|
||||
}
|
||||
default:
|
||||
if pY <= topY {
|
||||
return top
|
||||
} else {
|
||||
return bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ScrollView handling
|
||||
|
||||
private func lockScrollView() {
|
||||
guard let scrollView = scrollView else { return }
|
||||
|
||||
if isScrollLocked {
|
||||
log.debug("Already scroll locked.")
|
||||
return
|
||||
}
|
||||
isScrollLocked = true
|
||||
|
||||
scrollBouncable = scrollView.bounces
|
||||
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
|
||||
|
||||
scrollView.isDirectionalLockEnabled = true
|
||||
scrollView.bounces = false
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
}
|
||||
|
||||
private func unlockScrollView() {
|
||||
guard let scrollView = scrollView, isScrollLocked else { return }
|
||||
|
||||
isScrollLocked = false
|
||||
|
||||
scrollView.isDirectionalLockEnabled = false
|
||||
scrollView.bounces = scrollBouncable
|
||||
scrollView.showsVerticalScrollIndicator = scrollIndictorVisible
|
||||
}
|
||||
|
||||
private func fitToBounds(scrollView: UIScrollView) {
|
||||
log.debug("fit scroll view to bounds -- scroll offset =", scrollView.contentOffset.y)
|
||||
|
||||
surfaceView.frame.origin.y = layoutAdapter.topY - scrollView.contentOffset.y
|
||||
scrollView.transform = CGAffineTransform.identity.translatedBy(x: 0.0,
|
||||
y: scrollView.contentOffset.y)
|
||||
scrollView.scrollIndicatorInsets = UIEdgeInsets(top: -scrollView.contentOffset.y,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: 0.0)
|
||||
}
|
||||
|
||||
private func settle(scrollView: UIScrollView) {
|
||||
log.debug("settle scroll view")
|
||||
let frame = surfaceView.layer.presentation()?.frame ?? surfaceView.frame
|
||||
surfaceView.transform = .identity
|
||||
surfaceView.frame = frame
|
||||
scrollView.transform = .identity
|
||||
scrollView.frame = initialScrollFrame
|
||||
scrollView.contentOffset = scrollView.contentOffsetZero
|
||||
scrollView.scrollIndicatorInsets = .zero
|
||||
}
|
||||
|
||||
|
||||
private func stopScrollingWithDeceleration(at contentOffset: CGPoint) {
|
||||
// Must use setContentOffset(_:animated) to force-stop deceleration
|
||||
scrollView?.setContentOffset(contentOffset, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
fileprivate weak var floatingPanel: FloatingPanel?
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
if floatingPanel?.animator != nil {
|
||||
self.state = .began
|
||||
}
|
||||
}
|
||||
override weak var delegate: UIGestureRecognizerDelegate? {
|
||||
get {
|
||||
return super.delegate
|
||||
}
|
||||
set {
|
||||
guard newValue is FloatingPanel else {
|
||||
let exception = NSException(name: .invalidArgumentException,
|
||||
reason: "FloatingPanelController's built-in pan gesture recognizer must have its controller as its delegate.",
|
||||
userInfo: nil)
|
||||
exception.raise()
|
||||
return
|
||||
}
|
||||
super.delegate = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/26.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// A view that presents a backdrop interface behind a floating panel.
|
||||
public class FloatingPanelBackdropView: UIView {
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setUp()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setUp()
|
||||
}
|
||||
|
||||
private func setUp() {
|
||||
layer.backgroundColor = UIColor.black.cgColor
|
||||
}
|
||||
|
||||
@objc dynamic public override var backgroundColor: UIColor? {
|
||||
get {
|
||||
guard let color = layer.backgroundColor else { return nil }
|
||||
return UIColor(cgColor: color)
|
||||
}
|
||||
set { layer.backgroundColor = newValue?.cgColor }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/10/03.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public protocol FloatingPanelBehavior {
|
||||
/// Asks the behavior if the floating panel should project a momentum of a user interaction to move the proposed position.
|
||||
///
|
||||
/// The default implementation of this method returns true. This method is called for a layout to support all positions(tip, half and full).
|
||||
/// Therefore, `proposedTargetPosition` can only be `FloatingPanelPosition.tip` or `FloatingPanelPosition.full`.
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool
|
||||
|
||||
/// Returns a deceleration rate to calculate a target position projected a dragging momentum.
|
||||
///
|
||||
/// The default implementation of this method returns the normal deceleration rate of UIScrollView.
|
||||
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat
|
||||
|
||||
/// Returns the progress to redirect to the previous position.
|
||||
///
|
||||
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next position. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
|
||||
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to project a floating panel to a position on finger up if the user dragged.
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to add a floating panel to a position.
|
||||
///
|
||||
/// Its animator instance will be used to animate the surface view in `FloatingPanelController.addPanel(toParent:belowView:animated:)`.
|
||||
/// Default is an animator with ease-in-out curve and 0.25 sec duration.
|
||||
func addAnimator(_ fpc: FloatingPanelController, to: FloatingPanelPosition) -> UIViewPropertyAnimator
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to remove a floating panel from a position.
|
||||
///
|
||||
/// Its animator instance will be used to animate the surface view in `FloatingPanelController.removePanelFromParent(animated:completion:)`.
|
||||
/// Default is an animator with ease-in-out curve and 0.25 sec duration.
|
||||
func removeAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition) -> UIViewPropertyAnimator
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to move a floating panel from a position to a position.
|
||||
///
|
||||
/// Its animator instance will be used to animate the surface view in `FloatingPanelController.move(to:animated:completion:)`.
|
||||
/// Default is an animator with ease-in-out curve and 0.25 sec duration.
|
||||
func moveAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> UIViewPropertyAnimator
|
||||
|
||||
/// Returns a y-axis velocity to invoke a removal interaction at the bottom position.
|
||||
///
|
||||
/// Default is 10.0. This method is called when FloatingPanelController.isRemovalInteractionEnabled is true.
|
||||
var removalVelocity: CGFloat { get }
|
||||
|
||||
/// Returns the threshold of the transition to invoke a removal interaction at the bottom position.
|
||||
///
|
||||
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to invoke the removal interaction. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits. This method is called when FloatingPanelController.isRemovalInteractionEnabled is true.
|
||||
var removalProgress: CGFloat { get }
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to remove a floating panel with a velocity interactively at the bottom position.
|
||||
///
|
||||
/// Default is a spring animator with 1.0 damping ratio. This method is called when FloatingPanelController.isRemovalInteractionEnabled is true.
|
||||
func removalInteractionAnimator(_ fpc: FloatingPanelController, with velocity: CGVector) -> UIViewPropertyAnimator
|
||||
|
||||
|
||||
/// Asks the behavior whether the rubber band effect is enabled in moving over a given edge of the surface view.
|
||||
///
|
||||
/// This method allows the behavior to activate the rubber band effect to a given edge of the surface view. By default, the effect is disabled.
|
||||
func allowsRubberBanding(for edge: UIRectEdge) -> Bool
|
||||
}
|
||||
|
||||
public extension FloatingPanelBehavior {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
|
||||
switch (fpc.position, proposedTargetPosition) {
|
||||
case (.full, .tip):
|
||||
return false
|
||||
case (.tip, .full):
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat {
|
||||
#if swift(>=4.2)
|
||||
return UIScrollView.DecelerationRate.normal.rawValue
|
||||
#else
|
||||
return UIScrollViewDecelerationRateNormal
|
||||
#endif
|
||||
}
|
||||
|
||||
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat {
|
||||
return 0.5
|
||||
}
|
||||
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
return defaultBehavior.interactionAnimator(fpc, to: targetPosition, with: velocity)
|
||||
}
|
||||
|
||||
func addAnimator(_ fpc: FloatingPanelController, to: FloatingPanelPosition) -> UIViewPropertyAnimator {
|
||||
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
|
||||
}
|
||||
|
||||
func removeAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition) -> UIViewPropertyAnimator {
|
||||
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
|
||||
}
|
||||
|
||||
func moveAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> UIViewPropertyAnimator {
|
||||
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
|
||||
}
|
||||
|
||||
var removalVelocity: CGFloat {
|
||||
return 10.0
|
||||
}
|
||||
|
||||
var removalProgress: CGFloat {
|
||||
return 0.5
|
||||
}
|
||||
|
||||
func removalInteractionAnimator(_ fpc: FloatingPanelController, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
log.debug("velocity", velocity)
|
||||
let timing = UISpringTimingParameters(dampingRatio: 1.0,
|
||||
frequencyResponse: 0.3,
|
||||
initialVelocity: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
}
|
||||
|
||||
func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private let defaultBehavior = FloatingPanelDefaultBehavior()
|
||||
|
||||
public class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
|
||||
public init() { }
|
||||
|
||||
public func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
let timing = timeingCurve(with: velocity)
|
||||
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
animator.isInterruptible = false
|
||||
return animator
|
||||
}
|
||||
|
||||
private func timeingCurve(with velocity: CGVector) -> UITimingCurveProvider {
|
||||
log.debug("velocity", velocity)
|
||||
let damping = self.getDamping(with: velocity)
|
||||
return UISpringTimingParameters(dampingRatio: damping,
|
||||
frequencyResponse: 0.3,
|
||||
initialVelocity: velocity)
|
||||
}
|
||||
|
||||
private let velocityThreshold: CGFloat = 8.0
|
||||
private func getDamping(with velocity: CGVector) -> CGFloat {
|
||||
let dy = abs(velocity.dy)
|
||||
if dy > velocityThreshold {
|
||||
return 0.7
|
||||
} else {
|
||||
return 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public protocol FloatingPanelControllerDelegate: class {
|
||||
// if it returns nil, FloatingPanelController uses the default layout
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout?
|
||||
|
||||
// if it returns nil, FloatingPanelController uses the default behavior
|
||||
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior?
|
||||
|
||||
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) // changed the settled position in the model layer
|
||||
|
||||
/// Asks the delegate if dragging should begin by the pan gesture recognizer.
|
||||
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) // any surface frame changes in dragging
|
||||
|
||||
// called on start of dragging (may require some time and or distance to move)
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController)
|
||||
// called on finger up if the user dragged. velocity is in points/second.
|
||||
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition)
|
||||
func floatingPanelWillBeginDecelerating(_ vc: FloatingPanelController) // called on finger up as we are moving
|
||||
func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) // called when scroll view grinds to a halt
|
||||
|
||||
// called on start of dragging to remove its views from a parent view controller
|
||||
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint)
|
||||
// called when its views are removed from a parent view controller
|
||||
func floatingPanelDidEndRemove(_ vc: FloatingPanelController)
|
||||
|
||||
/// Asks the delegate if the other gesture recognizer should be allowed to recognize the gesture in parallel.
|
||||
///
|
||||
/// By default, any tap and long gesture recognizers are allowed to recognize gestures simultaneously.
|
||||
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
|
||||
}
|
||||
|
||||
public extension FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
return nil
|
||||
}
|
||||
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
|
||||
return nil
|
||||
}
|
||||
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {}
|
||||
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool {
|
||||
return true
|
||||
}
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) {}
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {}
|
||||
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {}
|
||||
func floatingPanelWillBeginDecelerating(_ vc: FloatingPanelController) {}
|
||||
func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) {}
|
||||
|
||||
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint) {}
|
||||
func floatingPanelDidEndRemove(_ vc: FloatingPanelController) {}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum FloatingPanelPosition: Int {
|
||||
case full
|
||||
case half
|
||||
case tip
|
||||
case hidden
|
||||
}
|
||||
|
||||
///
|
||||
/// A container view controller to display a floating panel to present contents in parallel as a user wants.
|
||||
///
|
||||
open class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
/// Constants indicating how safe area insets are added to the adjusted content inset.
|
||||
public enum ContentInsetAdjustmentBehavior: Int {
|
||||
case always
|
||||
case never
|
||||
}
|
||||
|
||||
/// The delegate of the floating panel controller object.
|
||||
public weak var delegate: FloatingPanelControllerDelegate?{
|
||||
didSet{
|
||||
didUpdateDelegate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
|
||||
public var surfaceView: FloatingPanelSurfaceView! {
|
||||
return floatingPanel.surfaceView
|
||||
}
|
||||
|
||||
/// Returns the backdrop view managed by the controller object.
|
||||
public var backdropView: FloatingPanelBackdropView! {
|
||||
return floatingPanel.backdropView
|
||||
}
|
||||
|
||||
/// Returns the scroll view that the controller tracks.
|
||||
public weak var scrollView: UIScrollView? {
|
||||
return floatingPanel.scrollView
|
||||
}
|
||||
|
||||
// The underlying gesture recognizer for pan gestures
|
||||
public var panGestureRecognizer: UIPanGestureRecognizer {
|
||||
return floatingPanel.panGestureRecognizer
|
||||
}
|
||||
|
||||
/// The current position of the floating panel controller's contents.
|
||||
public var position: FloatingPanelPosition {
|
||||
return floatingPanel.state
|
||||
}
|
||||
|
||||
/// The layout object managed by the controller
|
||||
public var layout: FloatingPanelLayout {
|
||||
return floatingPanel.layoutAdapter.layout
|
||||
}
|
||||
|
||||
/// The behavior object managed by the controller
|
||||
public var behavior: FloatingPanelBehavior {
|
||||
return floatingPanel.behavior
|
||||
}
|
||||
|
||||
/// The content insets of the tracking scroll view derived from this safe area
|
||||
public var adjustedContentInsets: UIEdgeInsets {
|
||||
return floatingPanel.layoutAdapter.adjustedContentInsets
|
||||
}
|
||||
|
||||
/// The behavior for determining the adjusted content offsets.
|
||||
///
|
||||
/// This property specifies how the content area of the tracking scroll view is modified using `adjustedContentInsets`. The default value of this property is FloatingPanelController.ContentInsetAdjustmentBehavior.always.
|
||||
public var contentInsetAdjustmentBehavior: ContentInsetAdjustmentBehavior = .always
|
||||
|
||||
/// A Boolean value that determines whether the removal interaction is enabled.
|
||||
public var isRemovalInteractionEnabled: Bool {
|
||||
set { floatingPanel.isRemovalInteractionEnabled = newValue }
|
||||
get { return floatingPanel.isRemovalInteractionEnabled }
|
||||
}
|
||||
|
||||
/// The view controller responsible for the content portion of the floating panel.
|
||||
public var contentViewController: UIViewController? {
|
||||
set { set(contentViewController: newValue) }
|
||||
get { return _contentViewController }
|
||||
}
|
||||
private var _contentViewController: UIViewController?
|
||||
|
||||
private var floatingPanel: FloatingPanel!
|
||||
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
|
||||
private var safeAreaInsetsObservation: NSKeyValueObservation?
|
||||
private let modalTransition = FloatingPanelModalTransition()
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setUp()
|
||||
}
|
||||
|
||||
/// Initialize a newly created floating panel controller.
|
||||
public init(delegate: FloatingPanelControllerDelegate? = nil) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.delegate = delegate
|
||||
setUp()
|
||||
}
|
||||
|
||||
private func setUp() {
|
||||
_ = FloatingPanelController.dismissSwizzling
|
||||
|
||||
modalPresentationStyle = .custom
|
||||
transitioningDelegate = modalTransition
|
||||
|
||||
floatingPanel = FloatingPanel(self,
|
||||
layout: fetchLayout(for: self.traitCollection),
|
||||
behavior: fetchBehavior(for: self.traitCollection))
|
||||
}
|
||||
|
||||
private func didUpdateDelegate(){
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.behavior = fetchBehavior(for: self.traitCollection)
|
||||
}
|
||||
|
||||
// MARK:- Overrides
|
||||
|
||||
/// Creates the view that the controller manages.
|
||||
open override func loadView() {
|
||||
assert(self.storyboard == nil, "Storyboard isn't supported")
|
||||
|
||||
let view = FloatingPanelPassThroughView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
backdropView.frame = view.bounds
|
||||
view.addSubview(backdropView)
|
||||
|
||||
surfaceView.frame = view.bounds
|
||||
view.addSubview(surfaceView)
|
||||
|
||||
self.view = view as UIView
|
||||
}
|
||||
|
||||
open override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {}
|
||||
else {
|
||||
// Because {top,bottom}LayoutGuide is managed as a view
|
||||
if preSafeAreaInsets != layoutInsets {
|
||||
self.update(safeAreaInsets: layoutInsets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
if view.translatesAutoresizingMaskIntoConstraints {
|
||||
view.frame.size = size
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.willTransition(to: newCollection, with: coordinator)
|
||||
|
||||
// Change layout for a new trait collection
|
||||
reloadLayout(for: newCollection)
|
||||
setUpLayout()
|
||||
|
||||
floatingPanel.behavior = fetchBehavior(for: newCollection)
|
||||
}
|
||||
|
||||
open override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
safeAreaInsetsObservation = nil
|
||||
}
|
||||
|
||||
// MARK:- Privates
|
||||
|
||||
private func fetchLayout(for traitCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
switch traitCollection.verticalSizeClass {
|
||||
case .compact:
|
||||
return self.delegate?.floatingPanel(self, layoutFor: traitCollection) ?? FloatingPanelDefaultLandscapeLayout()
|
||||
default:
|
||||
return self.delegate?.floatingPanel(self, layoutFor: traitCollection) ?? FloatingPanelDefaultLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchBehavior(for traitCollection: UITraitCollection) -> FloatingPanelBehavior {
|
||||
return self.delegate?.floatingPanel(self, behaviorFor: traitCollection) ?? FloatingPanelDefaultBehavior()
|
||||
}
|
||||
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
guard
|
||||
preSafeAreaInsets != safeAreaInsets,
|
||||
self.floatingPanel.isDecelerating == false
|
||||
else { return }
|
||||
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
|
||||
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
|
||||
preSafeAreaInsets = safeAreaInsets
|
||||
|
||||
setUpLayout()
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
scrollView?.contentInset = adjustedContentInsets
|
||||
scrollView?.scrollIndicatorInsets = adjustedContentInsets
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadLayout(for traitCollection: UITraitCollection) {
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.layoutAdapter.prepareLayout(in: self)
|
||||
|
||||
if let parent = self.parent {
|
||||
if let layout = layout as? UIViewController, layout == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the layout object. Don't let the parent adopt FloatingPanelLayout.")
|
||||
}
|
||||
if let behavior = behavior as? UIViewController, behavior == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the behavior object. Don't let the parent adopt FloatingPanelBehavior.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setUpLayout() {
|
||||
// preserve the current content offset
|
||||
let contentOffset = scrollView?.contentOffset
|
||||
|
||||
floatingPanel.layoutAdapter.updateHeight()
|
||||
floatingPanel.layoutAdapter.activateLayout(of: floatingPanel.state)
|
||||
|
||||
scrollView?.contentOffset = contentOffset ?? .zero
|
||||
}
|
||||
|
||||
// MARK: - Container view controller interface
|
||||
|
||||
/// Shows the surface view at the initial position defined by the current layout
|
||||
public func show(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
// Must apply the current layout here
|
||||
reloadLayout(for: traitCollection)
|
||||
setUpLayout()
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
// Must track the safeAreaInsets of `self.view` to update the layout.
|
||||
// There are 2 reasons.
|
||||
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (vc, change) in
|
||||
guard change.oldValue != change.newValue else { return }
|
||||
self?.update(safeAreaInsets: vc.layoutInsets)
|
||||
}
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
// Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()`
|
||||
}
|
||||
|
||||
move(to: floatingPanel.layoutAdapter.layout.initialPosition,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
/// Hides the surface view to the hidden position
|
||||
public func hide(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
safeAreaInsetsObservation = nil
|
||||
move(to: .hidden,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
/// Adds the view managed by the controller as a child of the specified view controller.
|
||||
/// - Parameters:
|
||||
/// - parent: A parent view controller object that displays FloatingPanelController's view. A container view controller object isn't applicable.
|
||||
/// - belowView: Insert the surface view managed by the controller below the specified view. By default, the surface view will be added to the end of the parent list of subviews.
|
||||
/// - animated: Pass true to animate the presentation; otherwise, pass false.
|
||||
public func addPanel(toParent parent: UIViewController, belowView: UIView? = nil, animated: Bool = false) {
|
||||
guard self.parent == nil else {
|
||||
log.warning("Already added to a parent(\(parent))")
|
||||
return
|
||||
}
|
||||
precondition((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.")
|
||||
precondition((parent is UITabBarController) == false, "UITabBarController displays child view controllers with a radio-style selection interface")
|
||||
precondition((parent is UISplitViewController) == false, "UISplitViewController manages two child view controllers in a master-detail interface")
|
||||
precondition((parent is UITableViewController) == false, "UITableViewController should not be the parent because the view is a table view so that a floating panel doens't work well")
|
||||
precondition((parent is UICollectionViewController) == false, "UICollectionViewController should not be the parent because the view is a collection view so that a floating panel doens't work well")
|
||||
|
||||
if let belowView = belowView {
|
||||
parent.view.insertSubview(self.view, belowSubview: belowView)
|
||||
} else {
|
||||
parent.view.addSubview(self.view)
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
parent.addChild(self)
|
||||
#else
|
||||
parent.addChildViewController(self)
|
||||
#endif
|
||||
|
||||
view.frame = parent.view.bounds // Needed for a correct safe area configuration
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
self.view.topAnchor.constraint(equalTo: parent.view.topAnchor, constant: 0.0),
|
||||
self.view.leftAnchor.constraint(equalTo: parent.view.leftAnchor, constant: 0.0),
|
||||
self.view.rightAnchor.constraint(equalTo: parent.view.rightAnchor, constant: 0.0),
|
||||
self.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0),
|
||||
])
|
||||
|
||||
show(animated: animated) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
#if swift(>=4.2)
|
||||
self.didMove(toParent: self)
|
||||
#else
|
||||
self.didMove(toParentViewController: self)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the controller and the managed view from its parent view controller
|
||||
/// - Parameters:
|
||||
/// - animated: Pass true to animate the presentation; otherwise, pass false.
|
||||
/// - completion: The block to execute after the view controller is dismissed. This block has no return value and takes no parameters. You may specify nil for this parameter.
|
||||
public func removePanelFromParent(animated: Bool, completion: (() -> Void)? = nil) {
|
||||
guard self.parent != nil else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
||||
hide(animated: animated) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
#if swift(>=4.2)
|
||||
self.willMove(toParent: nil)
|
||||
#else
|
||||
self.willMove(toParentViewController: nil)
|
||||
#endif
|
||||
|
||||
self.view.removeFromSuperview()
|
||||
|
||||
#if swift(>=4.2)
|
||||
self.removeFromParent()
|
||||
#else
|
||||
self.removeFromParentViewController()
|
||||
#endif
|
||||
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the position to the specified position.
|
||||
/// - Parameters:
|
||||
/// - to: Pass a FloatingPanelPosition value to move the surface view to the position.
|
||||
/// - animated: Pass true to animate the presentation; otherwise, pass false.
|
||||
/// - completion: The block to execute after the view controller has finished moving. This block has no return value and takes no parameters. You may specify nil for this parameter.
|
||||
public func move(to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
precondition(floatingPanel.layoutAdapter.vc != nil, "Use show(animated:completion)")
|
||||
floatingPanel.move(to: to, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
/// Sets the view controller responsible for the content portion of the floating panel.
|
||||
public func set(contentViewController: UIViewController?) {
|
||||
if let vc = _contentViewController {
|
||||
#if swift(>=4.2)
|
||||
vc.willMove(toParent: nil)
|
||||
#else
|
||||
vc.willMove(toParentViewController: nil)
|
||||
#endif
|
||||
|
||||
vc.view.removeFromSuperview()
|
||||
|
||||
#if swift(>=4.2)
|
||||
vc.removeFromParent()
|
||||
#else
|
||||
vc.removeFromParentViewController()
|
||||
#endif
|
||||
}
|
||||
|
||||
if let vc = contentViewController {
|
||||
#if swift(>=4.2)
|
||||
addChild(vc)
|
||||
#else
|
||||
addChildViewController(vc)
|
||||
#endif
|
||||
|
||||
let surfaceView = floatingPanel.surfaceView
|
||||
surfaceView.add(contentView: vc.view)
|
||||
|
||||
#if swift(>=4.2)
|
||||
vc.didMove(toParent: self)
|
||||
#else
|
||||
vc.didMove(toParentViewController: self)
|
||||
#endif
|
||||
}
|
||||
|
||||
_contentViewController = contentViewController
|
||||
}
|
||||
|
||||
@available(*, unavailable, renamed: "set(contentViewController:)")
|
||||
open override func show(_ vc: UIViewController, sender: Any?) {
|
||||
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) {
|
||||
target.show(vc, sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable, renamed: "set(contentViewController:)")
|
||||
open override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
|
||||
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.showDetailViewController(_:sender:)), sender: sender) {
|
||||
target.showDetailViewController(vc, sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Scroll view tracking
|
||||
|
||||
/// Tracks the specified scroll view to correspond with the scroll.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scrollView: Specify a scroll view to continuously and seamlessly work in concert with interactions of the surface view or nil to cancel it.
|
||||
public func track(scrollView: UIScrollView?) {
|
||||
guard let scrollView = scrollView else {
|
||||
floatingPanel.scrollView = nil
|
||||
return
|
||||
}
|
||||
|
||||
floatingPanel.scrollView = scrollView
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
if #available(iOS 11.0, *) {
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
} else {
|
||||
#if swift(>=4.2)
|
||||
children.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
#else
|
||||
childViewControllers.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
#endif
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Utilities
|
||||
|
||||
/// Updates the layout object from the delegate and lays out the views managed
|
||||
/// by the controller immediately.
|
||||
///
|
||||
/// This method updates the `FloatingPanelLayout` object from the delegate and
|
||||
/// then it calls `layoutIfNeeded()` of the root view to force the view
|
||||
/// to update the floating panel's layout immediately. It can be called in an
|
||||
/// animation block.
|
||||
public func updateLayout() {
|
||||
reloadLayout(for: traitCollection)
|
||||
setUpLayout()
|
||||
}
|
||||
|
||||
/// Returns the y-coordinate of the point at the origin of the surface view.
|
||||
public func originYOfSurface(for pos: FloatingPanelPosition) -> CGFloat {
|
||||
switch pos {
|
||||
case .full:
|
||||
return floatingPanel.layoutAdapter.topY
|
||||
case .half:
|
||||
return floatingPanel.layoutAdapter.middleY
|
||||
case .tip:
|
||||
return floatingPanel.layoutAdapter.bottomY
|
||||
case .hidden:
|
||||
return floatingPanel.layoutAdapter.hiddenY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatingPanelController {
|
||||
private static let dismissSwizzling: Any? = {
|
||||
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
|
||||
if let imp = class_getMethodImplementation(aClass, #selector(dismiss(animated:completion:))),
|
||||
let originalAltMethod = class_getInstanceMethod(aClass, #selector(fp_original_dismiss(animated:completion:))) {
|
||||
method_setImplementation(originalAltMethod, imp)
|
||||
}
|
||||
let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:)))
|
||||
let swizzledMethod = class_getInstanceMethod(aClass, #selector(fp_dismiss(animated:completion:)))
|
||||
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
|
||||
// switch implementation..
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
|
||||
public extension UIViewController {
|
||||
@objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
|
||||
}
|
||||
@objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
if let fpc = parent as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Call dismiss(animated:completion:) to FloatingPanelController directly
|
||||
if let fpc = self as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// For other view controllers
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/27.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// FloatingPanelFullScreenLayout
|
||||
///
|
||||
/// Use the layout protocol if you configure full, half and tip insets from the superview, not the safe area.
|
||||
/// It can't be used with FloatingPanelIntrinsicLayout.
|
||||
public protocol FloatingPanelFullScreenLayout: FloatingPanelLayout { }
|
||||
|
||||
/// FloatingPanelIntrinsicLayout
|
||||
///
|
||||
/// Use the layout protocol if you want to layout a panel using the intrinsic height.
|
||||
/// It can't be used with FloatingPanelFullScreenLayout.
|
||||
///
|
||||
/// - Attention:
|
||||
/// `insetFor(position:)` must return `nil` for the full position. Because
|
||||
/// the inset is determined automatically by the intrinsic height.
|
||||
/// You can customize insets only for the half, tip and hidden positions.
|
||||
public protocol FloatingPanelIntrinsicLayout: FloatingPanelLayout { }
|
||||
|
||||
public extension FloatingPanelIntrinsicLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .full
|
||||
}
|
||||
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return [.full]
|
||||
}
|
||||
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FloatingPanelLayout: class {
|
||||
/// Returns the initial position of a floating panel.
|
||||
var initialPosition: FloatingPanelPosition { get }
|
||||
|
||||
/// Returns a set of FloatingPanelPosition objects to tell the applicable
|
||||
/// positions of the floating panel controller.
|
||||
///
|
||||
/// By default, it returns all position except for `hidden` position. Because
|
||||
/// it's always supported by `FloatingPanelController` so you don't need to return it.
|
||||
var supportedPositions: Set<FloatingPanelPosition> { get }
|
||||
|
||||
/// Return the interaction buffer to the top from the top position. Default is 6.0.
|
||||
var topInteractionBuffer: CGFloat { get }
|
||||
|
||||
/// Return the interaction buffer to the bottom from the bottom position. Default is 6.0.
|
||||
var bottomInteractionBuffer: CGFloat { get }
|
||||
|
||||
/// Returns a CGFloat value to determine a Y coordinate of a floating panel for each position(full, half, tip and hidden).
|
||||
///
|
||||
/// Its returning value indicates a different inset for each position.
|
||||
/// For full position, a top inset from a safe area in `FloatingPanelController.view`.
|
||||
/// For half or tip position, a bottom inset from the safe area.
|
||||
/// For hidden position, a bottom inset from `FloatingPanelController.view`.
|
||||
/// If a position isn't supported or the default value is used, return nil.
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat?
|
||||
|
||||
/// Returns X-axis and width layout constraints of the surface view of a floating panel.
|
||||
/// You must not include any Y-axis and height layout constraints of the surface view
|
||||
/// because their constraints will be configured by the floating panel controller.
|
||||
/// By default, the width of a surface view fits a safe area.
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint]
|
||||
|
||||
/// Returns a CGFloat value to determine the backdrop view's alpha for a position.
|
||||
///
|
||||
/// Default is 0.3 at full position, otherwise 0.0.
|
||||
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat
|
||||
}
|
||||
|
||||
public extension FloatingPanelLayout {
|
||||
var topInteractionBuffer: CGFloat { return 6.0 }
|
||||
var bottomInteractionBuffer: CGFloat { return 6.0 }
|
||||
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return Set([.full, .half, .tip])
|
||||
}
|
||||
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.sideLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: view.sideLayoutGuide.rightAnchor, constant: 0.0),
|
||||
]
|
||||
}
|
||||
|
||||
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
|
||||
return position == .full ? 0.3 : 0.0
|
||||
}
|
||||
}
|
||||
|
||||
public class FloatingPanelDefaultLayout: FloatingPanelLayout {
|
||||
public init() { }
|
||||
|
||||
public var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
|
||||
public func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return 18.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 69.0
|
||||
case .hidden: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout {
|
||||
public init() { }
|
||||
|
||||
public var initialPosition: FloatingPanelPosition {
|
||||
return .tip
|
||||
}
|
||||
public var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return [.full, .tip]
|
||||
}
|
||||
|
||||
public func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return 16.0
|
||||
case .tip: return 69.0
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FloatingPanelLayoutAdapter {
|
||||
weak var vc: UIViewController!
|
||||
private weak var surfaceView: FloatingPanelSurfaceView!
|
||||
private weak var backdropView: FloatingPanelBackdropView!
|
||||
|
||||
var layout: FloatingPanelLayout {
|
||||
didSet {
|
||||
checkLayoutConsistance()
|
||||
}
|
||||
}
|
||||
|
||||
private var safeAreaInsets: UIEdgeInsets {
|
||||
return vc?.layoutInsets ?? .zero
|
||||
}
|
||||
|
||||
private var initialConst: CGFloat = 0.0
|
||||
|
||||
private var fixedConstraints: [NSLayoutConstraint] = []
|
||||
private var fullConstraints: [NSLayoutConstraint] = []
|
||||
private var halfConstraints: [NSLayoutConstraint] = []
|
||||
private var tipConstraints: [NSLayoutConstraint] = []
|
||||
private var offConstraints: [NSLayoutConstraint] = []
|
||||
private var interactiveTopConstraint: NSLayoutConstraint?
|
||||
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
|
||||
private var fullInset: CGFloat {
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
return intrinsicHeight
|
||||
} else {
|
||||
return layout.insetFor(position: .full) ?? 0.0
|
||||
}
|
||||
}
|
||||
private var halfInset: CGFloat {
|
||||
return layout.insetFor(position: .half) ?? 0.0
|
||||
}
|
||||
private var tipInset: CGFloat {
|
||||
return layout.insetFor(position: .tip) ?? 0.0
|
||||
}
|
||||
private var hiddenInset: CGFloat {
|
||||
return layout.insetFor(position: .hidden) ?? 0.0
|
||||
}
|
||||
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
var supportedPositions = layout.supportedPositions
|
||||
supportedPositions.remove(.hidden)
|
||||
return supportedPositions
|
||||
}
|
||||
|
||||
var topMostState: FloatingPanelPosition {
|
||||
if supportedPositions.contains(.full) {
|
||||
return .full
|
||||
}
|
||||
if supportedPositions.contains(.half) {
|
||||
return .half
|
||||
}
|
||||
return .tip
|
||||
}
|
||||
|
||||
var topY: CGFloat {
|
||||
if supportedPositions.contains(.full) {
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
return surfaceView.superview!.bounds.height - surfaceView.bounds.height
|
||||
case is FloatingPanelFullScreenLayout:
|
||||
return fullInset
|
||||
default:
|
||||
return (safeAreaInsets.top + fullInset)
|
||||
}
|
||||
} else {
|
||||
return middleY
|
||||
}
|
||||
}
|
||||
|
||||
var middleY: CGFloat {
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return surfaceView.superview!.bounds.height - halfInset
|
||||
} else{
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
|
||||
}
|
||||
}
|
||||
|
||||
var bottomY: CGFloat {
|
||||
if supportedPositions.contains(.tip) {
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return surfaceView.superview!.bounds.height - tipInset
|
||||
} else{
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset)
|
||||
}
|
||||
} else {
|
||||
return middleY
|
||||
}
|
||||
}
|
||||
|
||||
var hiddenY: CGFloat {
|
||||
return surfaceView.superview!.bounds.height
|
||||
}
|
||||
|
||||
var topMaxY: CGFloat {
|
||||
return layout is FloatingPanelFullScreenLayout ? 0.0 : safeAreaInsets.top
|
||||
}
|
||||
|
||||
var bottomMaxY: CGFloat {
|
||||
if layout is FloatingPanelFullScreenLayout{
|
||||
return surfaceView.superview!.bounds.height - hiddenInset
|
||||
} else {
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
|
||||
}
|
||||
}
|
||||
|
||||
var adjustedContentInsets: UIEdgeInsets {
|
||||
return UIEdgeInsets(top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: safeAreaInsets.bottom,
|
||||
right: 0.0)
|
||||
}
|
||||
|
||||
func positionY(for pos: FloatingPanelPosition) -> CGFloat {
|
||||
switch pos {
|
||||
case .full:
|
||||
return topY
|
||||
case .half:
|
||||
return middleY
|
||||
case .tip:
|
||||
return bottomY
|
||||
case .hidden:
|
||||
return hiddenY
|
||||
}
|
||||
}
|
||||
|
||||
var intrinsicHeight: CGFloat = 0.0
|
||||
|
||||
init(surfaceView: FloatingPanelSurfaceView, backdropView: FloatingPanelBackdropView, layout: FloatingPanelLayout) {
|
||||
self.layout = layout
|
||||
self.surfaceView = surfaceView
|
||||
self.backdropView = backdropView
|
||||
}
|
||||
|
||||
func updateIntrinsicHeight() {
|
||||
#if swift(>=4.2)
|
||||
let fittingSize = UIView.layoutFittingCompressedSize
|
||||
#else
|
||||
let fittingSize = UILayoutFittingCompressedSize
|
||||
#endif
|
||||
var intrinsicHeight = surfaceView.contentView?.systemLayoutSizeFitting(fittingSize).height ?? 0.0
|
||||
var safeAreaBottom: CGFloat = 0.0
|
||||
if #available(iOS 11.0, *) {
|
||||
safeAreaBottom = surfaceView.contentView?.safeAreaInsets.bottom ?? 0.0
|
||||
if safeAreaBottom > 0 {
|
||||
intrinsicHeight -= safeAreaInsets.bottom
|
||||
}
|
||||
}
|
||||
self.intrinsicHeight = max(intrinsicHeight, 0.0)
|
||||
|
||||
log.debug("Update intrinsic height =", intrinsicHeight,
|
||||
", surface(height) =", surfaceView.frame.height,
|
||||
", content(height) =", surfaceView.contentView?.frame.height ?? 0.0,
|
||||
", content safe area(bottom) =", safeAreaBottom)
|
||||
}
|
||||
|
||||
func prepareLayout(in vc: UIViewController) {
|
||||
self.vc = vc
|
||||
|
||||
NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
surfaceView.translatesAutoresizingMaskIntoConstraints = false
|
||||
backdropView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
// Fixed constraints of surface and backdrop views
|
||||
let surfaceConstraints = layout.prepareLayout(surfaceView: surfaceView, in: vc.view!)
|
||||
let backdropConstraints = [
|
||||
backdropView.topAnchor.constraint(equalTo: vc.view.topAnchor, constant: 0.0),
|
||||
backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor,constant: 0.0),
|
||||
backdropView.rightAnchor.constraint(equalTo: vc.view.rightAnchor, constant: 0.0),
|
||||
backdropView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor, constant: 0.0),
|
||||
]
|
||||
|
||||
fixedConstraints = surfaceConstraints + backdropConstraints
|
||||
|
||||
// Flexible surface constraints for full, half, tip and off
|
||||
let topAnchor: NSLayoutYAxisAnchor = {
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return vc.view.topAnchor
|
||||
} else {
|
||||
return vc.layoutGuide.topAnchor
|
||||
}
|
||||
}()
|
||||
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
// Set up on updateHeight()
|
||||
break
|
||||
default:
|
||||
fullConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: topAnchor,
|
||||
constant: fullInset),
|
||||
]
|
||||
}
|
||||
|
||||
let bottomAnchor: NSLayoutYAxisAnchor = {
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return vc.view.bottomAnchor
|
||||
} else {
|
||||
return vc.layoutGuide.bottomAnchor
|
||||
}
|
||||
}()
|
||||
|
||||
halfConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: bottomAnchor,
|
||||
constant: -halfInset),
|
||||
]
|
||||
tipConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: bottomAnchor,
|
||||
constant: -tipInset),
|
||||
]
|
||||
|
||||
offConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo:vc.view.bottomAnchor,
|
||||
constant: -hiddenInset),
|
||||
]
|
||||
}
|
||||
|
||||
func startInteraction(at state: FloatingPanelPosition) {
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
let interactiveTopConstraint: NSLayoutConstraint
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout,
|
||||
is FloatingPanelFullScreenLayout:
|
||||
initialConst = surfaceView.frame.minY
|
||||
interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.view.topAnchor,
|
||||
constant: initialConst)
|
||||
default:
|
||||
initialConst = surfaceView.frame.minY - safeAreaInsets.top
|
||||
interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
|
||||
constant: initialConst)
|
||||
}
|
||||
NSLayoutConstraint.activate([interactiveTopConstraint])
|
||||
self.interactiveTopConstraint = interactiveTopConstraint
|
||||
}
|
||||
|
||||
func endInteraction(at state: FloatingPanelPosition) {
|
||||
// Don't deactivate `interactiveTopConstraint` here because it leads to
|
||||
// unsatisfiable constraints
|
||||
}
|
||||
|
||||
// The method is separated from prepareLayout(to:) for the rotation support
|
||||
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
|
||||
func updateHeight() {
|
||||
guard let vc = vc else { return }
|
||||
|
||||
if let const = self.heightConstraint {
|
||||
NSLayoutConstraint.deactivate([const])
|
||||
}
|
||||
|
||||
let heightConstraint: NSLayoutConstraint
|
||||
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
updateIntrinsicHeight()
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom)
|
||||
default:
|
||||
let const = -(positionY(for: topMostState))
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
|
||||
constant: const)
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([heightConstraint])
|
||||
self.heightConstraint = heightConstraint
|
||||
|
||||
surfaceView.bottomOverflow = vc.view.bounds.height + layout.topInteractionBuffer
|
||||
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
NSLayoutConstraint.deactivate(fullConstraints)
|
||||
fullConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
|
||||
constant: -fullInset),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
func updateInteractiveTopConstraint(diff: CGFloat, allowsTopBuffer: Bool, with behavior: FloatingPanelBehavior) {
|
||||
defer {
|
||||
surfaceView.superview!.layoutIfNeeded() // MUST call here to update `surfaceView.frame`
|
||||
}
|
||||
|
||||
let topMostConst: CGFloat = {
|
||||
var ret: CGFloat = 0.0
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
|
||||
ret = topY
|
||||
default:
|
||||
ret = topY - safeAreaInsets.top
|
||||
}
|
||||
return max(ret, 0.0) // The top boundary is equal to the related topAnchor.
|
||||
}()
|
||||
let bottomMostConst: CGFloat = {
|
||||
var ret: CGFloat = 0.0
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
|
||||
ret = bottomY
|
||||
default:
|
||||
ret = bottomY - safeAreaInsets.top
|
||||
}
|
||||
return min(ret, bottomMaxY)
|
||||
}()
|
||||
let minConst = allowsTopBuffer ? topMostConst - layout.topInteractionBuffer : topMostConst
|
||||
let maxConst = bottomMostConst + layout.bottomInteractionBuffer
|
||||
|
||||
var const = initialConst + diff
|
||||
|
||||
// Rubberbanding top buffer
|
||||
if behavior.allowsRubberBanding(for: .top), const < topMostConst {
|
||||
let buffer = topMostConst - const
|
||||
const = topMostConst - rubberbandEffect(for: buffer, base: vc.view.bounds.height)
|
||||
}
|
||||
|
||||
// Rubberbanding bottom buffer
|
||||
if behavior.allowsRubberBanding(for: .bottom), const > bottomMostConst {
|
||||
let buffer = const - bottomMostConst
|
||||
const = bottomMostConst + rubberbandEffect(for: buffer, base: vc.view.bounds.height)
|
||||
}
|
||||
|
||||
interactiveTopConstraint?.constant = max(minConst, min(maxConst, const))
|
||||
}
|
||||
|
||||
// According to @chpwn's tweet: https://twitter.com/chpwn/status/285540192096497664
|
||||
// x = distance from the edge
|
||||
// c = constant value, UIScrollView uses 0.55
|
||||
// d = dimension, either width or height
|
||||
private func rubberbandEffect(for buffer: CGFloat, base: CGFloat) -> CGFloat {
|
||||
return (1.0 - (1.0 / ((buffer * 0.55 / base) + 1.0))) * base
|
||||
}
|
||||
|
||||
func activateLayout(of state: FloatingPanelPosition) {
|
||||
defer {
|
||||
surfaceView.superview!.layoutIfNeeded()
|
||||
}
|
||||
|
||||
var state = state
|
||||
|
||||
setBackdropAlpha(of: state)
|
||||
|
||||
// Must deactivate `interactiveTopConstraint` here
|
||||
if let interactiveTopConstraint = interactiveTopConstraint {
|
||||
NSLayoutConstraint.deactivate([interactiveTopConstraint])
|
||||
self.interactiveTopConstraint = nil
|
||||
}
|
||||
NSLayoutConstraint.activate(fixedConstraints)
|
||||
|
||||
if supportedPositions.union([.hidden]).contains(state) == false {
|
||||
state = layout.initialPosition
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
switch state {
|
||||
case .full:
|
||||
NSLayoutConstraint.activate(fullConstraints)
|
||||
case .half:
|
||||
NSLayoutConstraint.activate(halfConstraints)
|
||||
case .tip:
|
||||
NSLayoutConstraint.activate(tipConstraints)
|
||||
case .hidden:
|
||||
NSLayoutConstraint.activate(offConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
private func setBackdropAlpha(of target: FloatingPanelPosition) {
|
||||
if target == .hidden {
|
||||
self.backdropView.alpha = 0.0
|
||||
} else {
|
||||
self.backdropView.alpha = layout.backdropAlphaFor(position: target)
|
||||
}
|
||||
}
|
||||
|
||||
private func checkLayoutConsistance() {
|
||||
// Verify layout configurations
|
||||
assert(supportedPositions.count > 0)
|
||||
assert(supportedPositions.union([.hidden]).contains(layout.initialPosition),
|
||||
"Does not include an initial position (\(layout.initialPosition)) in supportedPositions (\(supportedPositions))")
|
||||
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout")
|
||||
}
|
||||
|
||||
if halfInset > 0 {
|
||||
assert(halfInset > tipInset, "Invalid half and tip insets")
|
||||
}
|
||||
// The verification isn't working on orientation change(portrait -> landscape)
|
||||
// of a floating panel in tab bar. Because the `safeAreaInsets.bottom` is
|
||||
// updated in delay so that it can be 83.0(not 53.0) even after the surface
|
||||
// and the super view's frame is fit to landscape already.
|
||||
/*if fullInset > 0 {
|
||||
assert(middleY > topY, "Invalid insets { topY: \(topY), middleY: \(middleY) }")
|
||||
assert(bottomY > topY, "Invalid insets { topY: \(topY), bottomY: \(bottomY) }")
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/26.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// A view that presents a surface interface in a floating panel.
|
||||
public class FloatingPanelSurfaceView: UIView {
|
||||
|
||||
/// A GrabberHandleView object displayed at the top of the surface view.
|
||||
///
|
||||
/// To use a custom grabber handle, hide this and then add the custom one
|
||||
/// to the surface view at appropriate coordinates.
|
||||
public let grabberHandle: GrabberHandleView = GrabberHandleView()
|
||||
|
||||
/// Offset of the grabber handle from the top
|
||||
@objc dynamic public var grabberTopPadding: CGFloat = 6.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
|
||||
/// The height of the grabber bar area
|
||||
public var topGrabberBarHeight: CGFloat {
|
||||
return grabberTopPadding * 2 + grabberHandleHeight
|
||||
}
|
||||
|
||||
/// Grabber view width and height
|
||||
@objc dynamic public var grabberHandleWidth: CGFloat = 36.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
@objc dynamic public var grabberHandleHeight: CGFloat = 5.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
|
||||
/// A root view of a content view controller
|
||||
public weak var contentView: UIView!
|
||||
|
||||
/// The content insets specifying the insets around the content view.
|
||||
///
|
||||
/// - important: Currently the `bottom` inset is ignored.
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
// Needs update constraints
|
||||
self.setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
private var color: UIColor? = .white { didSet { setNeedsLayout() } }
|
||||
var bottomOverflow: CGFloat = 0.0 // Must not call setNeedsLayout()
|
||||
|
||||
@objc dynamic public override var backgroundColor: UIColor? {
|
||||
get { return color }
|
||||
set { color = newValue }
|
||||
}
|
||||
|
||||
/// The radius to use when drawing top rounded corners.
|
||||
///
|
||||
/// `self.contentView` is masked with the top rounded corners automatically on iOS 11 and later.
|
||||
/// On iOS 10, they are not automatically masked because of a UIVisualEffectView issue. See https://forums.developer.apple.com/thread/50854
|
||||
@objc dynamic public var cornerRadius: CGFloat {
|
||||
set { containerView.layer.cornerRadius = newValue; setNeedsLayout() }
|
||||
get { return containerView.layer.cornerRadius }
|
||||
}
|
||||
|
||||
/// A Boolean indicating whether the surface shadow is displayed.
|
||||
@objc dynamic public var shadowHidden: Bool = false { didSet { setNeedsLayout() } }
|
||||
|
||||
/// The color of the surface shadow.
|
||||
@objc dynamic public var shadowColor: UIColor = .black { didSet { setNeedsLayout() } }
|
||||
|
||||
/// The offset (in points) of the surface shadow.
|
||||
@objc dynamic public var shadowOffset: CGSize = CGSize(width: 0.0, height: 1.0) { didSet { setNeedsLayout() } }
|
||||
|
||||
/// The opacity of the surface shadow.
|
||||
@objc dynamic public var shadowOpacity: Float = 0.2 { didSet { setNeedsLayout() } }
|
||||
|
||||
/// The blur radius (in points) used to render the surface shadow.
|
||||
@objc dynamic public var shadowRadius: CGFloat = 3 { didSet { setNeedsLayout() } }
|
||||
|
||||
/// The width of the surface border.
|
||||
@objc dynamic public var borderColor: UIColor? { didSet { setNeedsLayout() } }
|
||||
|
||||
/// The color of the surface border.
|
||||
@objc dynamic public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
|
||||
|
||||
/// Offset of the container view from the top
|
||||
@objc dynamic public var containerTopInset: CGFloat = 0.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
|
||||
/// The view presents an actual surface shape.
|
||||
///
|
||||
/// It renders the background color, border line and top rounded corners,
|
||||
/// specified by other properties. The reason why they're not be applied to
|
||||
/// a content view directly is because it avoids any side-effects to the
|
||||
/// content view.
|
||||
public let containerView: UIView = UIView()
|
||||
|
||||
@available(*, unavailable, renamed: "containerView")
|
||||
public var backgroundView: UIView!
|
||||
|
||||
private lazy var containerViewTopInsetConstraint: NSLayoutConstraint = containerView.topAnchor.constraint(equalTo: topAnchor, constant: containerTopInset)
|
||||
private lazy var containerViewHeightConstraint: NSLayoutConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
|
||||
|
||||
/// The content view top constraint
|
||||
private var contentViewTopConstraint: NSLayoutConstraint?
|
||||
/// The content view left constraint
|
||||
private var contentViewLeftConstraint: NSLayoutConstraint?
|
||||
/// The content right constraint
|
||||
private var contentViewRightConstraint: NSLayoutConstraint?
|
||||
/// The content height constraint
|
||||
private var contentViewHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
private lazy var grabberHandleWidthConstraint: NSLayoutConstraint = grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandleWidth)
|
||||
private lazy var grabberHandleHeightConstraint: NSLayoutConstraint = grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandleHeight)
|
||||
private lazy var grabberHandleTopConstraint: NSLayoutConstraint = grabberHandle.topAnchor.constraint(equalTo: topAnchor, constant: grabberTopPadding)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
addSubViews()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
addSubViews()
|
||||
}
|
||||
|
||||
private func addSubViews() {
|
||||
super.backgroundColor = .clear
|
||||
self.clipsToBounds = false
|
||||
|
||||
addSubview(containerView)
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
containerViewTopInsetConstraint,
|
||||
containerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
containerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
containerViewHeightConstraint,
|
||||
])
|
||||
|
||||
addSubview(grabberHandle)
|
||||
grabberHandle.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
grabberHandleTopConstraint,
|
||||
grabberHandle.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
public override func updateConstraints() {
|
||||
super.updateConstraints()
|
||||
containerViewTopInsetConstraint.constant = containerTopInset
|
||||
containerViewHeightConstraint.constant = bottomOverflow
|
||||
|
||||
contentViewTopConstraint?.constant = contentInsets.top
|
||||
contentViewLeftConstraint?.constant = contentInsets.left
|
||||
contentViewRightConstraint?.constant = contentInsets.right
|
||||
contentViewHeightConstraint?.constant = -containerTopInset
|
||||
|
||||
grabberHandleTopConstraint.constant = grabberTopPadding
|
||||
grabberHandleWidthConstraint.constant = grabberHandleWidth
|
||||
grabberHandleHeightConstraint.constant = grabberHandleHeight
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
log.debug("surface view frame = \(frame)")
|
||||
|
||||
containerView.backgroundColor = color
|
||||
|
||||
updateShadow()
|
||||
updateCornerRadius()
|
||||
updateBorder()
|
||||
}
|
||||
|
||||
private func updateShadow() {
|
||||
if shadowHidden == false {
|
||||
if #available(iOS 11, *) {
|
||||
// For clear background. See also, https://github.com/SCENEE/FloatingPanel/pull/51.
|
||||
layer.shadowColor = shadowColor.cgColor
|
||||
layer.shadowOffset = shadowOffset
|
||||
layer.shadowOpacity = shadowOpacity
|
||||
layer.shadowRadius = shadowRadius
|
||||
} else {
|
||||
// Can't update `layer.shadow*` directly because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user should display shadow appropriately.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateCornerRadius() {
|
||||
guard containerView.layer.cornerRadius != 0.0 else {
|
||||
containerView.layer.masksToBounds = false
|
||||
return
|
||||
}
|
||||
containerView.layer.masksToBounds = true
|
||||
if #available(iOS 11, *) {
|
||||
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
|
||||
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
|
||||
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
} else {
|
||||
// Can't use `containerView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user should display rounding corners appropriately.
|
||||
}
|
||||
}
|
||||
|
||||
private func updateBorder() {
|
||||
containerView.layer.borderColor = borderColor?.cgColor
|
||||
containerView.layer.borderWidth = borderWidth
|
||||
}
|
||||
|
||||
func add(contentView: UIView) {
|
||||
containerView.addSubview(contentView)
|
||||
self.contentView = contentView
|
||||
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor, constant: contentInsets.top)
|
||||
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: contentInsets.left)
|
||||
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: contentInsets.right)
|
||||
let heightConstraint = contentView.heightAnchor.constraint(equalTo: heightAnchor, constant: -containerTopInset)
|
||||
NSLayoutConstraint.activate([
|
||||
topConstraint,
|
||||
leftConstraint,
|
||||
rightConstraint,
|
||||
heightConstraint,
|
||||
])
|
||||
self.contentViewTopConstraint = topConstraint
|
||||
self.contentViewLeftConstraint = leftConstraint
|
||||
self.contentViewRightConstraint = rightConstraint
|
||||
self.contentViewHeightConstraint = heightConstraint
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,36 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/11/21.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ModalTransition: NSObject, UIViewControllerTransitioningDelegate {
|
||||
class FloatingPanelModalTransition: NSObject, UIViewControllerTransitioningDelegate {
|
||||
func animationController(forPresented presented: UIViewController,
|
||||
presenting: UIViewController,
|
||||
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return ModalPresentTransition()
|
||||
return FloatingPanelModalPresentTransition()
|
||||
}
|
||||
|
||||
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return ModalDismissTransition()
|
||||
return FloatingPanelModalDismissTransition()
|
||||
}
|
||||
|
||||
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
return PresentationController(presentedViewController: presented, presenting: presenting)
|
||||
return FloatingPanelPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
}
|
||||
}
|
||||
|
||||
class PresentationController: UIPresentationController {
|
||||
class FloatingPanelPresentationController: UIPresentationController {
|
||||
override func presentationTransitionWillBegin() {
|
||||
// Must call here even if duplicating on in containerViewWillLayoutSubviews()
|
||||
// Because it let the panel present correctly with the presentation animation
|
||||
// Because it let the floating panel present correctly with the presentation animation
|
||||
addFloatingPanel()
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
// For non-animated presentation
|
||||
if let fpc = presentedViewController as? FloatingPanelController, fpc.state == .hidden {
|
||||
if let fpc = presentedViewController as? FloatingPanelController, fpc.position == .hidden {
|
||||
fpc.show(animated: false, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -35,7 +38,7 @@ class PresentationController: UIPresentationController {
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
if let fpc = presentedViewController as? FloatingPanelController {
|
||||
// For non-animated dismissal
|
||||
if fpc.state != .hidden {
|
||||
if fpc.position != .hidden {
|
||||
fpc.hide(animated: false, completion: nil)
|
||||
}
|
||||
fpc.view.removeFromSuperview()
|
||||
@@ -44,15 +47,8 @@ class PresentationController: UIPresentationController {
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
guard
|
||||
let fpc = presentedViewController as? FloatingPanelController,
|
||||
/**
|
||||
This condition fixes https://github.com/SCENEE/FloatingPanel/issues/369.
|
||||
The issue is that this method is called in presenting a
|
||||
UIImagePickerViewController and then a FloatingPanelController
|
||||
view is added unnecessarily.
|
||||
*/
|
||||
fpc.presentedViewController == nil
|
||||
else { return }
|
||||
let fpc = presentedViewController as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
/*
|
||||
* Layout the views managed by `FloatingPanelController` here for the
|
||||
@@ -61,7 +57,11 @@ class PresentationController: UIPresentationController {
|
||||
addFloatingPanel()
|
||||
|
||||
// Forward touch events to the presenting view controller
|
||||
(fpc.view as? PassthroughView)?.eventForwardingView = presentingViewController.view
|
||||
(fpc.view as? FloatingPanelPassThroughView)?.eventForwardingView = presentingViewController.view
|
||||
|
||||
// Set tap-to-dismiss in the backdrop view
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
|
||||
fpc.backdropView.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
|
||||
@@ -80,70 +80,45 @@ class PresentationController: UIPresentationController {
|
||||
}
|
||||
}
|
||||
|
||||
class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
class FloatingPanelModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
|
||||
else { fatalError()}
|
||||
|
||||
let animator = fpc.animatorForPresenting(to: fpc.layout.initialState)
|
||||
let animator = fpc.behavior.addAnimator(fpc, to: fpc.layout.initialPosition)
|
||||
return TimeInterval(animator.duration)
|
||||
}
|
||||
|
||||
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard
|
||||
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
if let animator = fpc.transitionAnimator {
|
||||
return animator
|
||||
}
|
||||
|
||||
fpc.suspendTransitionAnimator(true)
|
||||
fpc.show(animated: true) { [weak fpc] in
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
fpc.show(animated: true) {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
guard let transitionAnimator = fpc.transitionAnimator else {
|
||||
fatalError("The panel state must be `hidden` but it is `\(fpc.state)`")
|
||||
}
|
||||
return transitionAnimator
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
class FloatingPanelModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
|
||||
else { fatalError()}
|
||||
|
||||
let animator = fpc.animatorForDismissing(with: .zero)
|
||||
let animator = fpc.behavior.removeAnimator(fpc, from: fpc.position)
|
||||
return TimeInterval(animator.duration)
|
||||
}
|
||||
|
||||
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard
|
||||
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
if let animator = fpc.transitionAnimator {
|
||||
return animator
|
||||
}
|
||||
|
||||
fpc.suspendTransitionAnimator(true)
|
||||
fpc.hide(animated: true) { [weak fpc] in
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
fpc.hide(animated: true) {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
return fpc.transitionAnimator!
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/11/21.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc(FloatingPanelPassthroughView)
|
||||
class PassthroughView: UIView {
|
||||
class FloatingPanelPassThroughView: UIView {
|
||||
public weak var eventForwardingView: UIView?
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let hitView = super.hitTest(point, with: event)
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/19.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// A view that presents a grabber handle in the surface of a panel.
|
||||
@objc(FloatingPanelGrabberView)
|
||||
public class GrabberView: UIView {
|
||||
public class GrabberHandleView: UIView {
|
||||
|
||||
public var barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0) { didSet { backgroundColor = barColor } }
|
||||
@objc dynamic public var barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0) {
|
||||
didSet { backgroundColor = barColor }
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
@@ -29,5 +32,6 @@ public class GrabberView: UIView {
|
||||
|
||||
private func render() {
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.cornerRadius = frame.size.height * 0.5
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.5.5</string>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/10/09.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
// Must be a variable to use `hook` property in testing
|
||||
var log = {
|
||||
return Logger()
|
||||
}()
|
||||
@@ -21,9 +23,9 @@ struct Logger {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug:
|
||||
return "Debug:"
|
||||
return "D/"
|
||||
case .info:
|
||||
return "Info:"
|
||||
return "I/"
|
||||
case .warning:
|
||||
return "Warning:"
|
||||
case .error:
|
||||
@@ -52,24 +54,23 @@ struct Logger {
|
||||
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
|
||||
}
|
||||
|
||||
private func log(_ level: Level, _ message: Any, _ arguments: [Any], tag: String, function: String, line: UInt) {
|
||||
private func log(_ level: Level, _ message: Any, _ arguments: [Any], function: String, line: UInt) {
|
||||
_ = s.wait(timeout: .now() + 0.033)
|
||||
defer { s.signal() }
|
||||
|
||||
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
|
||||
let _tag = tag.isEmpty ? "" : "\(tag):"
|
||||
let log: String = {
|
||||
switch level {
|
||||
case .debug:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage) (\(function):\(line))"
|
||||
return "\(level.displayName) \(message) \(extraMessage) (\(function):\(line))"
|
||||
default:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage)"
|
||||
return "\(level.displayName) \(message) \(extraMessage)"
|
||||
}
|
||||
}()
|
||||
|
||||
hook?(log, level)
|
||||
|
||||
os_log("%{public}@", log: osLog, type: level.osLogType, log)
|
||||
os_log("%@", log: osLog, type: level.osLogType, log)
|
||||
}
|
||||
|
||||
private func getPrettyFunction(_ function: String, _ file: String) -> String {
|
||||
@@ -80,21 +81,21 @@ struct Logger {
|
||||
}
|
||||
}
|
||||
|
||||
func debug(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
func debug(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
#if __FP_LOG
|
||||
self.log(.debug, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
self.log(.debug, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
#endif
|
||||
}
|
||||
|
||||
func info(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.info, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
func info(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.info, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func warning(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.warning, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
self.log(.warning, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.error, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
self.log(.error, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol LayoutGuideProvider {
|
||||
var topAnchor: NSLayoutYAxisAnchor { get }
|
||||
var bottomAnchor: NSLayoutYAxisAnchor { get }
|
||||
}
|
||||
extension UILayoutGuide: LayoutGuideProvider {}
|
||||
|
||||
class CustomLayoutGuide: LayoutGuideProvider {
|
||||
let topAnchor: NSLayoutYAxisAnchor
|
||||
let bottomAnchor: NSLayoutYAxisAnchor
|
||||
init(topAnchor: NSLayoutYAxisAnchor, bottomAnchor: NSLayoutYAxisAnchor) {
|
||||
self.topAnchor = topAnchor
|
||||
self.bottomAnchor = bottomAnchor
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
var layoutInsets: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets(top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
var layoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view!.safeAreaLayoutGuide
|
||||
} else {
|
||||
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol SideLayoutGuideProvider {
|
||||
var leftAnchor: NSLayoutXAxisAnchor { get }
|
||||
var rightAnchor: NSLayoutXAxisAnchor { get }
|
||||
}
|
||||
|
||||
extension UIView: SideLayoutGuideProvider {}
|
||||
extension UILayoutGuide: SideLayoutGuideProvider {}
|
||||
|
||||
// The reason why UIView has no extensions of safe area insets and top/bottom guides
|
||||
// is for iOS10 compat.
|
||||
extension UIView {
|
||||
var sideLayoutGuide: SideLayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return safeAreaLayoutGuide
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
func disableAutoLayout() {
|
||||
let frame = self.frame
|
||||
translatesAutoresizingMaskIntoConstraints = true
|
||||
self.frame = frame
|
||||
}
|
||||
func enableAutoLayout() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}
|
||||
|
||||
#if __FP_LOG
|
||||
#if swift(>=4.2)
|
||||
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .began: return "began"
|
||||
case .changed: return "changed"
|
||||
case .failed: return "failed"
|
||||
case .cancelled: return "cancelled"
|
||||
case .ended: return "endeded"
|
||||
case .possible: return "possible"
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
extension UIGestureRecognizerState: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .began: return "began"
|
||||
case .changed: return "changed"
|
||||
case .failed: return "failed"
|
||||
case .cancelled: return "cancelled"
|
||||
case .ended: return "endeded"
|
||||
case .possible: return "possible"
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extension UIScrollView {
|
||||
var contentOffsetZero: CGPoint {
|
||||
return CGPoint(x: 0.0, y: 0.0 - contentInset.top)
|
||||
}
|
||||
}
|
||||
|
||||
extension UISpringTimingParameters {
|
||||
public convenience init(dampingRatio: CGFloat, frequencyResponse: CGFloat, initialVelocity: CGVector = .zero) {
|
||||
let mass = 1 as CGFloat
|
||||
let stiffness = pow(2 * .pi / frequencyResponse, 2) * mass
|
||||
let damp = 4 * .pi * dampingRatio * mass / frequencyResponse
|
||||
self.init(mass: mass, stiffness: stiffness, damping: damp, initialVelocity: initialVelocity)
|
||||
}
|
||||
}
|
||||
|
||||
extension CGPoint {
|
||||
static var nan: CGPoint {
|
||||
return CGPoint(x: CGFloat.nan,
|
||||
y: CGFloat.nan)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/11/01.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@UIApplicationMain
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user