Full Code of faberNovel/DynamicOverlay for AI

main 2276a60762a8 cached
69 files
145.5 KB
38.3k tokens
1 requests
Download .txt
Repository: faberNovel/DynamicOverlay
Branch: main
Commit: 2276a60762a8
Files: 69
Total size: 145.5 KB

Directory structure:
gitextract_s05uf70j/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── CHANGELOG.md
├── DynamicOverlay.podspec
├── DynamicOverlay_Example/
│   ├── DynamicOverlay_Example/
│   │   ├── Classes/
│   │   │   └── MapApp.swift
│   │   ├── Configuration/
│   │   │   └── Info.plist
│   │   ├── Resources/
│   │   │   ├── Assets.xcassets/
│   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   └── Base.lproj/
│   │   │       └── LaunchScreen.storyboard
│   │   ├── UIKit/
│   │   │   └── UIKitAppDelegate.swift
│   │   └── View/
│   │       ├── ActionCell.swift
│   │       ├── BackdropView.swift
│   │       ├── FavoriteCell.swift
│   │       ├── MapRootView.swift
│   │       ├── MapView.swift
│   │       ├── OverlayBackgroundView.swift
│   │       ├── OverlayView.swift
│   │       └── SearchBar.swift
│   └── DynamicOverlay_Example.xcodeproj/
│       ├── project.pbxproj
│       ├── project.xcworkspace/
│       │   ├── contents.xcworkspacedata
│       │   └── xcshareddata/
│       │       ├── IDEWorkspaceChecks.plist
│       │       └── swiftpm/
│       │           └── Package.resolved
│       └── xcshareddata/
│           └── xcschemes/
│               └── DynamicOverlay_Example.xcscheme
├── Gemfile
├── LICENSE
├── Package.resolved
├── Package.swift
├── Package.xcconfig
├── README.md
├── Source/
│   ├── DynamicOverlay.h
│   ├── Info.plist
│   ├── Internal/
│   │   ├── DynamicOverlayBehaviorValue.swift
│   │   ├── DynamicOverlayNotchTransition+DynamicOverlayBehavior.swift
│   │   ├── Handle/
│   │   │   ├── ActivatedOverlayArea.swift
│   │   │   ├── ActiveOverlayAreaViewModifier.swift
│   │   │   ├── Drag/
│   │   │   │   ├── DynamicOverlayDragArea.swift
│   │   │   │   └── OnDragAreaChangeViewModifier.swift
│   │   │   ├── DrivingScrollView/
│   │   │   │   ├── DynamicOverlayScrollViewProxy.swift
│   │   │   │   └── OnDrivingScrollViewChangeViewModifier.swift
│   │   │   └── OverlayContainerCoordinateSpace.swift
│   │   ├── MagneticNotchOverlayBehaviorValue.swift
│   │   ├── OverlayContainer/
│   │   │   ├── DynamicOverlayContainerAnimationController.swift
│   │   │   ├── OverlayContainerCoordinator.swift
│   │   │   ├── OverlayContainerDynamicOverlayView.swift
│   │   │   ├── OverlayContainerRepresentableAdaptor.swift
│   │   │   ├── OverlayContainerStateDiffer.swift
│   │   │   └── SwiftUIOverlayContainerRepresentableAdaptor.swift
│   │   ├── OverlayContentModifier.swift
│   │   ├── OverlayNotchIndexMapper.swift
│   │   └── Utils/
│   │       └── Binding+CaseIterable.swift
│   └── Public/
│       ├── DynamicOverlayBehavior.swift
│       ├── DynamicOverlayHandle.swift
│       ├── DynamicOverlayModifier.swift
│       └── MagneticNotchOverlayBehavior.swift
├── Tests/
│   └── DynamicOverlayTests/
│       ├── DragHandleViewModifierTests.swift
│       ├── DrivingScrollViewModifierTests.swift
│       ├── MagneticNotchOverlayBehaviorValueTests.swift
│       ├── NotchBindingDynamicOverlayTests.swift
│       ├── NotchDimensionDynamicOverlayTests.swift
│       ├── NotchTranslationDynamicOverlayTests.swift
│       ├── OverlayContainerRepresentableAdaptorTests.swift
│       ├── OverlayNotchIndexMapperTests.swift
│       └── Utils/
│           ├── ValuePublisher.swift
│           ├── View+Measure.swift
│           ├── ViewInspector.swift
│           └── ViewRenderer.swift
└── fastlane/
    ├── Fastfile
    └── Pluginfile

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on: [push]

jobs:
  build:
    runs-on: macOS-latest

    steps:
      - uses: actions/checkout@v2
      - name: Bundle install
        working-directory: ./
        run: bundle install

      - name: Unit tests
        run: bundle exec fastlane tests

      - name: SPM lint
        run: bundle exec fastlane spm_lint

      - name: Carthage lint
        run: bundle exec fastlane carthage_lint

      - name: Pod lint
        run: bundle exec fastlane pod_lint


================================================
FILE: .github/workflows/release.yml
================================================

name: Release

on:
  workflow_dispatch:
    inputs:
          name:
            description: 'Version name'
            required: true

jobs:
  build:
    runs-on: macOS-latest
    steps:
    - uses: maxim-lobanov/setup-xcode@v1
      with:
        xcode-version: latest-stable

    - name: Checkout
      uses: actions/checkout@v2

    - name: Bundle install
      working-directory: ./
      run: bundle install

    - name: Release
      env:
        LC_ALL: en_US.UTF-8
        LANG: en_US.UTF-8
        GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN_CI }}
        COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_GZ_TOKEN }}
        GIT_COMMITTER_NAME: Bot Fabernovel
        GIT_AUTHOR_NAME: Bot Fabernovel
        GIT_COMMITTER_EMAIL: ci@fabernovel.com
        GIT_AUTHOR_EMAIL: ci@fabernovel.com
      run: bundle exec fastlane release version:${{ github.event.inputs.name }} bypass_confirmations:true


================================================
FILE: .gitignore
================================================
# OS X
.DS_Store

# Xcode

## Build generated
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

*.xccheckout
profile
## Other
*.moved-aside
*.xccheckout
DerivedData
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
!Carthage/Build/**/*.dSYM

# Bundler
.bundle

# Swift Package Manager
.build
.swiftpm

Example/Pods/
Pods/

.idea/

# fastlane specific
fastlane/report.xml

# deliver temporary files
fastlane/Preview.html

# snapshot generated screenshots
fastlane/screenshots/**/*.png
fastlane/screenshots/screenshots.html

# scan temporary files
fastlane/test_output
test_output
fastlane/.env
pre-change.yml
.build
fastlane/README.md


================================================
FILE: CHANGELOG.md
================================================
## [1.0.2]

### Fixed

- Jumping animation issue. #42

## [1.0.0]

### Fixed

- Overlay with multiple scroll views #16

## [1.0.0-beta.10]

### Fixed

- Crash with drivingScrollView a List and a condition #21
- Bump OverlayContainer to `3.5.2`


================================================
FILE: DynamicOverlay.podspec
================================================
#
# Be sure to run `pod lib lint DynamicOverlay.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
  s.name             = 'DynamicOverlay'
  s.version          = '1.0.2'
  s.summary          = 'OverlayContainer is a SwiftUI library which makes it easier to develop overlay based interfaces.'
  s.swift_version    = "5.0"

  s.description      = <<-DESC
  OverlayContainer is a SwiftUI library written in Swift. It makes it easier to develop overlay based interfaces, such as the one presented in the Apple Maps, Stocks or Shortcuts apps.
                       DESC

  s.homepage         = 'https://github.com/fabernovel/DynamicOverlay'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'gaetanzanella' => 'gaetan.zanella@fabernovel.com' }
  s.source           = { :git => 'https://github.com/fabernovel/DynamicOverlay.git', :tag => s.version.to_s }
  s.dependency       'OverlayContainer', '~> 3.5'

  s.ios.deployment_target = '13.0'
  s.source_files = 'Source/**/*.swift'
end


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/Classes/MapApp.swift
================================================
//
//  MapApp.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 05/03/2019.
//  Copyright © 2019 Fabernovel. All rights reserved.
//

import UIKit
import SwiftUI
import DynamicOverlay

@main
struct MapApp: App {

    @UIApplicationDelegateAdaptor(UIKitAppDelegate.self)
    private var delegate: UIKitAppDelegate

    var body: some Scene {
        WindowGroup {
            MapRootView()
        }
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/Configuration/Info.plist
================================================
<?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>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>


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "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"
  }
}

================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/Resources/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/Resources/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
        <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>


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/UIKit/UIKitAppDelegate.swift
================================================
//
//  UIKitAppDelegate.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 18/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import UIKit

class UIKitAppDelegate: NSObject, UIApplicationDelegate {

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        UITableView.appearance().backgroundColor = .systemBackground
        return true
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/ActionCell.swift
================================================
//
//  ActionCell.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 19/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI

struct ActionCell: View {

    var body: some View {
        Label("New Guide…", systemImage: "plus")
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/BackdropView.swift
================================================
//
//  Backdropview.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 19/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI

struct BackdropView: View {

    var body: some View {
        Color.black.opacity(0.3)
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/FavoriteCell.swift
================================================
//
//  FavoriteCell.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 19/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI

struct FavoriteCell: View {

    let imageName: String
    let title: String

    var body: some View {
        VStack {
            Circle()
                .foregroundColor(Color(.secondarySystemFill))
                .frame(width: 70, height: 70)
                .overlay(Image(systemName: imageName).font(.title2).foregroundColor(.blue))
            Text(title)
        }
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/MapRootView.swift
================================================
//
//  MapRootView.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 17/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI
import DynamicOverlay

enum Notch: CaseIterable, Equatable {
    case min, max
}

struct MapRootView: View {

    struct State {
        var notch: Notch = .min
        var isEditing = false
        var progress = 0.0
    }

    @SwiftUI.State
    private var state = State()

    // MARK: - View

    var body: some View {
        background
            .dynamicOverlay(overlay)
            .dynamicOverlayBehavior(behavior)
            .ignoresSafeArea()
    }

    // MARK: - Private

    private var behavior: some DynamicOverlayBehavior {
        MagneticNotchOverlayBehavior<Notch> { notch in
            switch notch {
            case .max:
                return .fractional(0.8)
            case .min:
                return .fractional(0.3)
            }
        }
        .disable(.min, state.isEditing)
        .notchChange($state.notch)
        .onTranslation { translation in
            state.progress = translation.progress
        }
    }

    private var background: some View {
        ZStack {
            MapView()
            BackdropView().opacity(state.progress)
        }
        .ignoresSafeArea()
    }

    private var overlay: some View {
        OverlayView { event in
            switch event {
            case .didBeginEditing:
                state.isEditing = true
                withAnimation { state.notch = .max }
            case .didEndEditing:
                state.isEditing = false
                withAnimation { state.notch = .min }
            }
        }
        .drivingScrollView()
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/MapView.swift
================================================
//
//  MapView.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 17/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import MapKit
import SwiftUI

struct MapView: View {

    var body: some View {
        MapViewAdaptor().ignoresSafeArea()
    }
}

private struct MapViewAdaptor: UIViewRepresentable {

    func makeUIView(context: Context) -> MKMapView {
        MKMapView()
    }

    func updateUIView(_ uiView: MKMapView, context: Context) {}
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/OverlayBackgroundView.swift
================================================
//
//  OverlayBackgroundView.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 19/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import SwiftUI

struct OverlayBackgroundView: View {

    var body: some View {
        Color(.systemBackground)
            .cornerRadius(8.0, corners: [.topLeft, .topRight])
            .shadow(color: Color.black.opacity(0.3), radius: 8.0)
    }
}

private extension View {

    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners))
    }
}

private struct RoundedCorner: Shape {

    var radius: CGFloat = 0.0
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        Path(
            UIBezierPath(
                roundedRect: rect,
                byRoundingCorners: corners,
                cornerRadii: CGSize(width: radius, height: radius)
            )
            .cgPath
        )
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/OverlayView.swift
================================================
//
//  OverlayView.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 17/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI
import DynamicOverlay

struct OverlayView: View {

    enum Event {
        case didBeginEditing
        case didEndEditing
    }

    let eventHandler: (Event) -> Void

    // MARK: - View

    var body: some View {
        VStack(spacing: 0.0) {
            header.draggable()
            list
        }
        .background(OverlayBackgroundView())
    }

    // MARK: - Private

    private var list: some View {
        List {
            Section(header: Text("Favorites")) {
                ScrollView(.horizontal) {
                    HStack {
                        FavoriteCell(imageName: "house.fill", title: "House")
                        FavoriteCell(imageName: "briefcase.fill", title: "Work")
                        FavoriteCell(imageName: "plus", title: "Add")
                    }
                }
            }
            Section(header: Text("My Guides")) {
                ActionCell()
            }
        }
        .listStyle(GroupedListStyle())
    }

    private var header: some View {
        SearchBar { event in
            switch event {
            case .didBeginEditing:
                eventHandler(.didBeginEditing)
            case .didCancel:
                eventHandler(.didEndEditing)
            }
        }
    }
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example/View/SearchBar.swift
================================================
//
//  SearchBar.swift
//  DynamicOverlay_Example
//
//  Created by Gaétan Zanella on 18/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI
import UIKit

struct SearchBar: View {

    enum Event {
        case didBeginEditing
        case didCancel
    }

    let eventHandler: (Event) -> Void

    var body: some View {
        SearchBarAdaptor(
            didBeginEditing: { eventHandler(.didBeginEditing) },
            didCancel: { eventHandler(.didCancel) }
        )
    }
}

private class SearchBarCoordinator: NSObject, UISearchBarDelegate {

    var didBeginEditing: (() -> Void)?
    var didCancel: (() -> Void)?

    // MARK: - UISearchBarDelegate

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        didBeginEditing?()
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.endEditing(true)
        didCancel?()
    }
}

private struct SearchBarAdaptor: UIViewRepresentable {

    let didBeginEditing: () -> Void
    let didCancel: () -> Void

    func makeCoordinator() -> SearchBarCoordinator {
        let coordinator = SearchBarCoordinator()
        coordinator.didBeginEditing = didBeginEditing
        coordinator.didCancel = didCancel
        return coordinator
    }

    func makeUIView(context: Context) -> UISearchBar {
        let searchBar = UISearchBar()
        searchBar.searchBarStyle = .minimal
        searchBar.showsCancelButton = true
        searchBar.placeholder = "Search for a place or address"
        searchBar.delegate = context.coordinator
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: Context) {}
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 52;
	objects = {

/* Begin PBXBuildFile section */
		E739AD94291D45F00076B2AC /* DynamicOverlay in Frameworks */ = {isa = PBXBuildFile; productRef = E739AD93291D45F00076B2AC /* DynamicOverlay */; };
		E73A7CE9262B2A8400959344 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E73A7CE8262B2A8400959344 /* MapView.swift */; };
		E73A7CEC262B2AA400959344 /* MapRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E73A7CEB262B2AA400959344 /* MapRootView.swift */; };
		E750EE4E262B2B4800E79C6B /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E750EE4D262B2B4800E79C6B /* OverlayView.swift */; };
		E750EE51262C30F600E79C6B /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E750EE50262C30F600E79C6B /* SearchBar.swift */; };
		E750EE63262C463100E79C6B /* MapApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E742E3C422302B4B002A2BED /* MapApp.swift */; };
		E750EE68262C69A900E79C6B /* UIKitAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E750EE67262C69A900E79C6B /* UIKitAppDelegate.swift */; };
		E750EE96262D772700E79C6B /* OverlayBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E750EE95262D772700E79C6B /* OverlayBackgroundView.swift */; };
		E750EE98262D798F00E79C6B /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E750EE97262D798F00E79C6B /* ActionCell.swift */; };
		E750EE9A262D799B00E79C6B /* FavoriteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E750EE99262D799B00E79C6B /* FavoriteCell.swift */; };
		E750EE9C262D7C9000E79C6B /* BackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E750EE9B262D7C9000E79C6B /* BackdropView.swift */; };
		E7691574222EA78B00FDEE7F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E7691573222EA78B00FDEE7F /* Assets.xcassets */; };
		E7691577222EA78B00FDEE7F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E7691575222EA78B00FDEE7F /* LaunchScreen.storyboard */; };
		E79705A0292F83100047839F /* OverlayContainerRepresentableAdaptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7970598292F83100047839F /* OverlayContainerRepresentableAdaptorTests.swift */; };
		E79705A1292F83100047839F /* NotchDimensionDynamicOverlayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7970599292F83100047839F /* NotchDimensionDynamicOverlayTests.swift */; };
		E79705A2292F83100047839F /* OverlayNotchIndexMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E797059A292F83100047839F /* OverlayNotchIndexMapperTests.swift */; };
		E79705A3292F83100047839F /* DrivingScrollViewModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E797059B292F83100047839F /* DrivingScrollViewModifierTests.swift */; };
		E79705A4292F83100047839F /* DragHandleViewModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E797059C292F83100047839F /* DragHandleViewModifierTests.swift */; };
		E79705A5292F83100047839F /* NotchTranslationDynamicOverlayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E797059D292F83100047839F /* NotchTranslationDynamicOverlayTests.swift */; };
		E79705A6292F83100047839F /* MagneticNotchOverlayBehaviorValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E797059E292F83100047839F /* MagneticNotchOverlayBehaviorValueTests.swift */; };
		E79705A7292F83100047839F /* NotchBindingDynamicOverlayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E797059F292F83100047839F /* NotchBindingDynamicOverlayTests.swift */; };
		E79705AC292F83190047839F /* ViewInspector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E79705A8292F83190047839F /* ViewInspector.swift */; };
		E79705AD292F83190047839F /* ValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = E79705A9292F83190047839F /* ValuePublisher.swift */; };
		E79705AE292F83190047839F /* View+Measure.swift in Sources */ = {isa = PBXBuildFile; fileRef = E79705AA292F83190047839F /* View+Measure.swift */; };
		E79705AF292F83190047839F /* ViewRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E79705AB292F83190047839F /* ViewRenderer.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
		E7970593292F817F0047839F /* PBXContainerItemProxy */ = {
			isa = PBXContainerItemProxy;
			containerPortal = E7691561222EA78A00FDEE7F /* Project object */;
			proxyType = 1;
			remoteGlobalIDString = E7691568222EA78A00FDEE7F;
			remoteInfo = DynamicOverlay_Example;
		};
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
		E739AD92291D45B10076B2AC /* DynamicOverlay */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DynamicOverlay; path = ..; sourceTree = "<group>"; };
		E73A7CE8262B2A8400959344 /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; };
		E73A7CEB262B2AA400959344 /* MapRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRootView.swift; sourceTree = "<group>"; };
		E741EE722576B10D0073FF6B /* DynamicOverlay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DynamicOverlay.framework; sourceTree = BUILT_PRODUCTS_DIR; };
		E742E3C422302B4B002A2BED /* MapApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MapApp.swift; path = Classes/MapApp.swift; sourceTree = "<group>"; };
		E750EE4D262B2B4800E79C6B /* OverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = "<group>"; };
		E750EE50262C30F600E79C6B /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
		E750EE67262C69A900E79C6B /* UIKitAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitAppDelegate.swift; sourceTree = "<group>"; };
		E750EE95262D772700E79C6B /* OverlayBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayBackgroundView.swift; sourceTree = "<group>"; };
		E750EE97262D798F00E79C6B /* ActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCell.swift; sourceTree = "<group>"; };
		E750EE99262D799B00E79C6B /* FavoriteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteCell.swift; sourceTree = "<group>"; };
		E750EE9B262D7C9000E79C6B /* BackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackdropView.swift; sourceTree = "<group>"; };
		E7691569222EA78A00FDEE7F /* DynamicOverlay_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DynamicOverlay_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
		E7691573222EA78B00FDEE7F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		E7691576222EA78B00FDEE7F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		E7691578222EA78B00FDEE7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		E797058F292F817F0047839F /* DynamicOverlay_ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DynamicOverlay_ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
		E7970598292F83100047839F /* OverlayContainerRepresentableAdaptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OverlayContainerRepresentableAdaptorTests.swift; path = ../../Tests/DynamicOverlayTests/OverlayContainerRepresentableAdaptorTests.swift; sourceTree = "<group>"; };
		E7970599292F83100047839F /* NotchDimensionDynamicOverlayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotchDimensionDynamicOverlayTests.swift; path = ../../Tests/DynamicOverlayTests/NotchDimensionDynamicOverlayTests.swift; sourceTree = "<group>"; };
		E797059A292F83100047839F /* OverlayNotchIndexMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OverlayNotchIndexMapperTests.swift; path = ../../Tests/DynamicOverlayTests/OverlayNotchIndexMapperTests.swift; sourceTree = "<group>"; };
		E797059B292F83100047839F /* DrivingScrollViewModifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DrivingScrollViewModifierTests.swift; path = ../../Tests/DynamicOverlayTests/DrivingScrollViewModifierTests.swift; sourceTree = "<group>"; };
		E797059C292F83100047839F /* DragHandleViewModifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DragHandleViewModifierTests.swift; path = ../../Tests/DynamicOverlayTests/DragHandleViewModifierTests.swift; sourceTree = "<group>"; };
		E797059D292F83100047839F /* NotchTranslationDynamicOverlayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotchTranslationDynamicOverlayTests.swift; path = ../../Tests/DynamicOverlayTests/NotchTranslationDynamicOverlayTests.swift; sourceTree = "<group>"; };
		E797059E292F83100047839F /* MagneticNotchOverlayBehaviorValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MagneticNotchOverlayBehaviorValueTests.swift; path = ../../Tests/DynamicOverlayTests/MagneticNotchOverlayBehaviorValueTests.swift; sourceTree = "<group>"; };
		E797059F292F83100047839F /* NotchBindingDynamicOverlayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotchBindingDynamicOverlayTests.swift; path = ../../Tests/DynamicOverlayTests/NotchBindingDynamicOverlayTests.swift; sourceTree = "<group>"; };
		E79705A8292F83190047839F /* ViewInspector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewInspector.swift; path = ../../Tests/DynamicOverlayTests/Utils/ViewInspector.swift; sourceTree = "<group>"; };
		E79705A9292F83190047839F /* ValuePublisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ValuePublisher.swift; path = ../../Tests/DynamicOverlayTests/Utils/ValuePublisher.swift; sourceTree = "<group>"; };
		E79705AA292F83190047839F /* View+Measure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "View+Measure.swift"; path = "../../Tests/DynamicOverlayTests/Utils/View+Measure.swift"; sourceTree = "<group>"; };
		E79705AB292F83190047839F /* ViewRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewRenderer.swift; path = ../../Tests/DynamicOverlayTests/Utils/ViewRenderer.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		E7691566222EA78A00FDEE7F /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				E739AD94291D45F00076B2AC /* DynamicOverlay in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		E797058C292F817F0047839F /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		E739AD91291D45B10076B2AC /* Packages */ = {
			isa = PBXGroup;
			children = (
				E739AD92291D45B10076B2AC /* DynamicOverlay */,
			);
			name = Packages;
			sourceTree = "<group>";
		};
		E73A7CEA262B2A8B00959344 /* View */ = {
			isa = PBXGroup;
			children = (
				E73A7CEB262B2AA400959344 /* MapRootView.swift */,
				E73A7CE8262B2A8400959344 /* MapView.swift */,
				E750EE4D262B2B4800E79C6B /* OverlayView.swift */,
				E750EE50262C30F600E79C6B /* SearchBar.swift */,
				E750EE95262D772700E79C6B /* OverlayBackgroundView.swift */,
				E750EE97262D798F00E79C6B /* ActionCell.swift */,
				E750EE99262D799B00E79C6B /* FavoriteCell.swift */,
				E750EE9B262D7C9000E79C6B /* BackdropView.swift */,
			);
			path = View;
			sourceTree = "<group>";
		};
		E741EE712576B10D0073FF6B /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				E741EE722576B10D0073FF6B /* DynamicOverlay.framework */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		E750EE66262C699C00E79C6B /* UIKit */ = {
			isa = PBXGroup;
			children = (
				E750EE67262C69A900E79C6B /* UIKitAppDelegate.swift */,
			);
			path = UIKit;
			sourceTree = "<group>";
		};
		E7691560222EA78A00FDEE7F = {
			isa = PBXGroup;
			children = (
				E739AD91291D45B10076B2AC /* Packages */,
				E769156B222EA78A00FDEE7F /* DynamicOverlay_Example */,
				E7970590292F817F0047839F /* DynamicOverlay_ExampleTests */,
				E769156A222EA78A00FDEE7F /* Products */,
				E741EE712576B10D0073FF6B /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		E769156A222EA78A00FDEE7F /* Products */ = {
			isa = PBXGroup;
			children = (
				E7691569222EA78A00FDEE7F /* DynamicOverlay_Example.app */,
				E797058F292F817F0047839F /* DynamicOverlay_ExampleTests.xctest */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		E769156B222EA78A00FDEE7F /* DynamicOverlay_Example */ = {
			isa = PBXGroup;
			children = (
				E742E3C422302B4B002A2BED /* MapApp.swift */,
				E750EE66262C699C00E79C6B /* UIKit */,
				E73A7CEA262B2A8B00959344 /* View */,
				E7691582222EA7C100FDEE7F /* Resources */,
				E7691581222EA7B400FDEE7F /* Configuration */,
			);
			path = DynamicOverlay_Example;
			sourceTree = "<group>";
		};
		E7691581222EA7B400FDEE7F /* Configuration */ = {
			isa = PBXGroup;
			children = (
				E7691578222EA78B00FDEE7F /* Info.plist */,
			);
			path = Configuration;
			sourceTree = "<group>";
		};
		E7691582222EA7C100FDEE7F /* Resources */ = {
			isa = PBXGroup;
			children = (
				E7691575222EA78B00FDEE7F /* LaunchScreen.storyboard */,
				E7691573222EA78B00FDEE7F /* Assets.xcassets */,
			);
			path = Resources;
			sourceTree = "<group>";
		};
		E7970590292F817F0047839F /* DynamicOverlay_ExampleTests */ = {
			isa = PBXGroup;
			children = (
				E797059C292F83100047839F /* DragHandleViewModifierTests.swift */,
				E797059B292F83100047839F /* DrivingScrollViewModifierTests.swift */,
				E797059E292F83100047839F /* MagneticNotchOverlayBehaviorValueTests.swift */,
				E797059F292F83100047839F /* NotchBindingDynamicOverlayTests.swift */,
				E7970599292F83100047839F /* NotchDimensionDynamicOverlayTests.swift */,
				E797059D292F83100047839F /* NotchTranslationDynamicOverlayTests.swift */,
				E7970598292F83100047839F /* OverlayContainerRepresentableAdaptorTests.swift */,
				E797059A292F83100047839F /* OverlayNotchIndexMapperTests.swift */,
				E79705A9292F83190047839F /* ValuePublisher.swift */,
				E79705AA292F83190047839F /* View+Measure.swift */,
				E79705A8292F83190047839F /* ViewInspector.swift */,
				E79705AB292F83190047839F /* ViewRenderer.swift */,
			);
			path = DynamicOverlay_ExampleTests;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		E7691568222EA78A00FDEE7F /* DynamicOverlay_Example */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = E769157B222EA78B00FDEE7F /* Build configuration list for PBXNativeTarget "DynamicOverlay_Example" */;
			buildPhases = (
				E7691565222EA78A00FDEE7F /* Sources */,
				E7691566222EA78A00FDEE7F /* Frameworks */,
				E7691567222EA78A00FDEE7F /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = DynamicOverlay_Example;
			packageProductDependencies = (
				E739AD93291D45F00076B2AC /* DynamicOverlay */,
			);
			productName = DynamicOverlay_Example;
			productReference = E7691569222EA78A00FDEE7F /* DynamicOverlay_Example.app */;
			productType = "com.apple.product-type.application";
		};
		E797058E292F817F0047839F /* DynamicOverlay_ExampleTests */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = E7970595292F817F0047839F /* Build configuration list for PBXNativeTarget "DynamicOverlay_ExampleTests" */;
			buildPhases = (
				E797058B292F817F0047839F /* Sources */,
				E797058C292F817F0047839F /* Frameworks */,
				E797058D292F817F0047839F /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
				E7970594292F817F0047839F /* PBXTargetDependency */,
			);
			name = DynamicOverlay_ExampleTests;
			productName = DynamicOverlay_ExampleTests;
			productReference = E797058F292F817F0047839F /* DynamicOverlay_ExampleTests.xctest */;
			productType = "com.apple.product-type.bundle.unit-test";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		E7691561222EA78A00FDEE7F /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 1410;
				LastUpgradeCheck = 1220;
				ORGANIZATIONNAME = Fabernovel;
				TargetAttributes = {
					E7691568222EA78A00FDEE7F = {
						CreatedOnToolsVersion = 10.1;
					};
					E797058E292F817F0047839F = {
						CreatedOnToolsVersion = 14.1;
						LastSwiftMigration = 1410;
						TestTargetID = E7691568222EA78A00FDEE7F;
					};
				};
			};
			buildConfigurationList = E7691564222EA78A00FDEE7F /* Build configuration list for PBXProject "DynamicOverlay_Example" */;
			compatibilityVersion = "Xcode 9.3";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = E7691560222EA78A00FDEE7F;
			productRefGroup = E769156A222EA78A00FDEE7F /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				E7691568222EA78A00FDEE7F /* DynamicOverlay_Example */,
				E797058E292F817F0047839F /* DynamicOverlay_ExampleTests */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		E7691567222EA78A00FDEE7F /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				E7691577222EA78B00FDEE7F /* LaunchScreen.storyboard in Resources */,
				E7691574222EA78B00FDEE7F /* Assets.xcassets in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		E797058D292F817F0047839F /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		E7691565222EA78A00FDEE7F /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				E750EE68262C69A900E79C6B /* UIKitAppDelegate.swift in Sources */,
				E750EE98262D798F00E79C6B /* ActionCell.swift in Sources */,
				E73A7CEC262B2AA400959344 /* MapRootView.swift in Sources */,
				E73A7CE9262B2A8400959344 /* MapView.swift in Sources */,
				E750EE9A262D799B00E79C6B /* FavoriteCell.swift in Sources */,
				E750EE4E262B2B4800E79C6B /* OverlayView.swift in Sources */,
				E750EE63262C463100E79C6B /* MapApp.swift in Sources */,
				E750EE96262D772700E79C6B /* OverlayBackgroundView.swift in Sources */,
				E750EE9C262D7C9000E79C6B /* BackdropView.swift in Sources */,
				E750EE51262C30F600E79C6B /* SearchBar.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		E797058B292F817F0047839F /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				E79705A2292F83100047839F /* OverlayNotchIndexMapperTests.swift in Sources */,
				E79705A7292F83100047839F /* NotchBindingDynamicOverlayTests.swift in Sources */,
				E79705AD292F83190047839F /* ValuePublisher.swift in Sources */,
				E79705A1292F83100047839F /* NotchDimensionDynamicOverlayTests.swift in Sources */,
				E79705A4292F83100047839F /* DragHandleViewModifierTests.swift in Sources */,
				E79705A3292F83100047839F /* DrivingScrollViewModifierTests.swift in Sources */,
				E79705A0292F83100047839F /* OverlayContainerRepresentableAdaptorTests.swift in Sources */,
				E79705AF292F83190047839F /* ViewRenderer.swift in Sources */,
				E79705A5292F83100047839F /* NotchTranslationDynamicOverlayTests.swift in Sources */,
				E79705AC292F83190047839F /* ViewInspector.swift in Sources */,
				E79705A6292F83100047839F /* MagneticNotchOverlayBehaviorValueTests.swift in Sources */,
				E79705AE292F83190047839F /* View+Measure.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
		E7970594292F817F0047839F /* PBXTargetDependency */ = {
			isa = PBXTargetDependency;
			target = E7691568222EA78A00FDEE7F /* DynamicOverlay_Example */;
			targetProxy = E7970593292F817F0047839F /* PBXContainerItemProxy */;
		};
/* End PBXTargetDependency section */

/* Begin PBXVariantGroup section */
		E7691575222EA78B00FDEE7F /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				E7691576222EA78B00FDEE7F /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		E7691579222EA78B00FDEE7F /* 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 = 15.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;
		};
		E769157A222EA78B00FDEE7F /* 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 = 15.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;
		};
		E769157C222EA78B00FDEE7F /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_STYLE = Automatic;
				INFOPLIST_FILE = DynamicOverlay_Example/Configuration/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 15;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = "com.fabernovel.DynamicOverlay-Example";
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 4.2;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		E769157D222EA78B00FDEE7F /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_STYLE = Automatic;
				INFOPLIST_FILE = DynamicOverlay_Example/Configuration/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 15;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = "com.fabernovel.DynamicOverlay-Example";
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 4.2;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
		E7970596292F817F0047839F /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				BUNDLE_LOADER = "$(TEST_HOST)";
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = C7G63Q6LZ9;
				GENERATE_INFOPLIST_FILE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = "com.gaetanzanella.DynamicOverlay-ExampleTests";
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = NO;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DynamicOverlay_Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DynamicOverlay_Example";
			};
			name = Debug;
		};
		E7970597292F817F0047839F /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				BUNDLE_LOADER = "$(TEST_HOST)";
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = C7G63Q6LZ9;
				GENERATE_INFOPLIST_FILE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = "com.gaetanzanella.DynamicOverlay-ExampleTests";
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = NO;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DynamicOverlay_Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DynamicOverlay_Example";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		E7691564222EA78A00FDEE7F /* Build configuration list for PBXProject "DynamicOverlay_Example" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				E7691579222EA78B00FDEE7F /* Debug */,
				E769157A222EA78B00FDEE7F /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		E769157B222EA78B00FDEE7F /* Build configuration list for PBXNativeTarget "DynamicOverlay_Example" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				E769157C222EA78B00FDEE7F /* Debug */,
				E769157D222EA78B00FDEE7F /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		E7970595292F817F0047839F /* Build configuration list for PBXNativeTarget "DynamicOverlay_ExampleTests" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				E7970596292F817F0047839F /* Debug */,
				E7970597292F817F0047839F /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCSwiftPackageProductDependency section */
		E739AD93291D45F00076B2AC /* DynamicOverlay */ = {
			isa = XCSwiftPackageProductDependency;
			productName = DynamicOverlay;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = E7691561222EA78A00FDEE7F /* Project object */;
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?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>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
================================================
{
  "object": {
    "pins": [
      {
        "package": "OverlayContainer",
        "repositoryURL": "https://github.com/applidium/OverlayContainer.git",
        "state": {
          "branch": null,
          "revision": "f1c8fc38bb1ad9a810397f1d06f7026a35e0760c",
          "version": "3.5.2"
        }
      }
    ]
  },
  "version": 1
}


================================================
FILE: DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/xcshareddata/xcschemes/DynamicOverlay_Example.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1400"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "E7691568222EA78A00FDEE7F"
               BuildableName = "DynamicOverlay_Example.app"
               BlueprintName = "DynamicOverlay_Example"
               ReferencedContainer = "container:DynamicOverlay_Example.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "E797058E292F817F0047839F"
               BuildableName = "DynamicOverlay_ExampleTests.xctest"
               BlueprintName = "DynamicOverlay_ExampleTests"
               ReferencedContainer = "container:DynamicOverlay_Example.xcodeproj">
            </BuildableReference>
         </TestableReference>
      </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 = "E7691568222EA78A00FDEE7F"
            BuildableName = "DynamicOverlay_Example.app"
            BlueprintName = "DynamicOverlay_Example"
            ReferencedContainer = "container:DynamicOverlay_Example.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "E7691568222EA78A00FDEE7F"
            BuildableName = "DynamicOverlay_Example.app"
            BlueprintName = "DynamicOverlay_Example"
            ReferencedContainer = "container:DynamicOverlay_Example.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'

gem "cocoapods", "~> 1.11"
gem "fastlane", "~> 2.1"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)


================================================
FILE: LICENSE
================================================
Copyright (c) 2020 Fabernovel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: Package.resolved
================================================
{
  "object": {
    "pins": [
      {
        "package": "OverlayContainer",
        "repositoryURL": "https://github.com/applidium/OverlayContainer.git",
        "state": {
          "branch": null,
          "revision": "f1c8fc38bb1ad9a810397f1d06f7026a35e0760c",
          "version": "3.5.2"
        }
      }
    ]
  },
  "version": 1
}


================================================
FILE: Package.swift
================================================
// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "DynamicOverlay",
    platforms: [
        .iOS(.v13)
    ],
    products: [
        .library(
            name: "DynamicOverlay",
            targets: ["DynamicOverlay"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/applidium/OverlayContainer.git", from: "3.5.2")
    ],
    targets: [
        .target(
            name: "DynamicOverlay",
            dependencies: ["OverlayContainer"],
            path: "Source"
        ),
        .testTarget(
            name: "DynamicOverlayTests",
            dependencies: [
                "DynamicOverlay",
            ]
        )
    ],
    swiftLanguageVersions: [.v5]
)


================================================
FILE: Package.xcconfig
================================================
IPHONEOS_DEPLOYMENT_TARGET = 10.0

SDKROOT =
SUPPORTED_PLATFORMS = iphoneos iphonesimulator
TARGETED_DEVICE_FAMILY = 1,2
VALID_ARCHS[sdk=iphoneos*] = arm64 armv7 armv7s
VALID_ARCHS[sdk=iphonesimulator*] = i386 x86_64

CODE_SIGN_IDENTITY =
CODE_SIGN_STYLE = Manual
INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks
SKIP_INSTALL = YES
DYLIB_COMPATIBILITY_VERSION = 1
DYLIB_CURRENT_VERSION = 1
DYLIB_INSTALL_NAME_BASE = @rpath
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks
DEFINES_MODULE = NO


================================================
FILE: README.md
================================================
# DynamicOverlay

<H4 align="center">
DynamicOverlay is a SwiftUI library. It makes easier to develop overlay based interfaces, such as the one presented in the Apple Maps, Stocks or Shortcuts apps.
</H4>

<p align="center">
  <a href="https://developer.apple.com/"><img alt="Platform" src="https://img.shields.io/badge/platform-iOS-green.svg"/></a>
  <a href="https://developer.apple.com/swift"><img alt="Swift5" src="https://img.shields.io/badge/language-Swift%205.0-orange.svg"/></a>
  <a href="https://cocoapods.org/pods/DynamicOverlay"><img alt="CocoaPods" src="https://img.shields.io/cocoapods/v/DynamicOverlay.svg?style=flat"/></a>
  <a href="https://github.com/Carthage/Carthage"><img alt="Carthage" src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat"/></a>
  <a href="https://github.com/fabernovel/DynamicOverlay/actions"><img alt="Build Status" src="https://github.com/fabernovel/DynamicOverlay/workflows/CI/badge.svg?branch=main"/></a>
  <a href="https://github.com/fabernovel/DynamicOverlay/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/cocoapods/l/DynamicOverlay.svg?style=flat"/></a>
</p>

---

- [Requirements](#requirements)
- [Getting started](#getting-started)
- [Examples](#examples)
- [Magnetic notch overlay](#magnetic-notch-overlay)
  - [Specifying the notches](#specifying-the-notches)
  - [Drag gesture support](#drag-gesture-support)
  - [Scroll view support](#scroll-view-support)
  - [Responding to overlay update](#responding-to-overlay-update)
  - [Moving the overlay](#moving-the-overlay)
  - [Disabling notches](#disabling-notches)
  - [Installation](#installation)
    - [CocoaPods](#cocoapods)
    - [Carthage](#carthage)
    - [Swift Package Manager](#swift-package-manager)
- [Under the hood](#under-the-hood)
- [Release](#release)
- [Author](#author)
- [License](#license)

## Requirements

`DynamicOverlay` is written in Swift 5. Compatible with iOS 13.0+.

## Getting started

A dynamic overlay is an overlay that dynamically reveals or hides the content underneath it.

You add a dynamic overlay as a [regular one](https://developer.apple.com/documentation/swiftui/view/overlay(_:alignment:)) using a view modifier:

```swift
Color.blue.dynamicOverlay(Color.red)
```
Its behavior is defined by the `DynamicOverlayBehavior` associated to it if any.

```swift

Color.blue
    .dynamicOverlay(Color.red)
    .dynamicOverlayBehavior(myOverlayBehavior)

var myOverlayBehavior: some DynamicOverlayBehavior {
    ...
}
```
If you do not specify a behavior in the overlay view hierarchy, it uses a default one.

## Examples

- [Map App](https://github.com/faberNovel/DynamicOverlay/blob/main/DynamicOverlay_Example/DynamicOverlay_Example/View/MapRootView.swift)

| Min | Max |
| ------------------- | ------------------ |
| <img src="https://github.com/faberNovel/DynamicOverlay/blob/main/Screenshots/min.png" width=200 /> | <img src="https://github.com/faberNovel/DynamicOverlay/blob/main/Screenshots/max.png" width=200 /> |

## Magnetic notch overlay

`MagneticNotchOverlayBehavior` is a `DynamicOverlayBehavior` instance. It is the only behavior available for now.

It describes an overlay that can be dragged up and down alongside predefined notches. Whenever a drag gesture ends, the overlay motion will continue until it reaches one of its notches.

### Specifying the notches

The preferred way to define the notches is to declare an `CaseIterable` enum:

```swift
enum Notch: CaseIterable, Equatable {
    case min, max
}
```
You specify the dimensions of each notch when you create a  `MagneticNotchOverlayBehavior`  instance:

```swift
@State var isCompact = false

var myOverlayBehavior: some DynamicOverlayBehavior {
    MagneticNotchOverlayBehavior<Notch> { notch in
        switch notch {
        case .max:
            return isCompact ? .fractional(0.5) : .fractional(0.8)
        case .min:
            return .fractional(0.3)
        }
    }
}
```
There are two kinds of dimension:
```swift
extension NotchDimension {

    /// Creates a dimension with an absolute point value.
    static func absolute(_ value: Double) -> NotchDimension

    /// Creates a dimension that is computed as a fraction of the height of the overlay parent view.
    static func fractional(_ value: Double) -> NotchDimension
}
```
### Drag gesture support

By default, all the content of the overlay is draggable but you can limit this behavior using the `draggable`  view modifier.

Here only the list header is draggable:

```swift
var body: some View {
    Color.green
        .dynamicOverlay(myOverlayContent)
        .dynamicOverlayBehavior(myOverlayBehavior)
}

var myOverlayContent: some View {
    VStack {
        Text("Header").draggable()
        List {
            Text("Row 1")
            Text("Row 2")
            Text("Row 3")
        }
    }
}

var myOverlayBehavior: some DynamicOverlayBehavior {
    MagneticNotchOverlayBehavior<Notch> { ... }
}
```
Here we disable the drag gesture entirely:
```swift
var myOverlayContent: some View {
    VStack {
        Text("Header")
        List {
            Text("Row 1")
            Text("Row 2")
            Text("Row 3")
        }
    }
    .draggable(false)
}
```

### Scroll view support

A magnetic notch overlay can coordinate its motion with the scrolling of a scroll view.

Mark the ScrollView or List that should dictate the overlays movement with `divingScrollView()`.

```swift
var myOverlayContent: some View {
    VStack {
        Text("Header").draggable()
        List {
            Text("Row 1")
            Text("Row 2")
            Text("Row 3")
        }
        .drivingScrollView()
    }
}
```

### Responding to overlay updates

You can track the overlay motions using the `onTranslation(_:)` view modifier. It is a great occasion to update your UI based on the current overlay state.

Here we define a control that should be right above the overlay:

```swift
struct ControlView: View {

    let height: CGFloat
    let action: () -> Void

    var body: some View {
        VStack {
            Button("Action", action: action)
            Spacer().frame(height: height)
        }
    }
}
```
We make sure the control is always visible thanks to the translation parameter:

```swift
@State var height: CGFloat = 0.0

var body: some View {
    ZStack {
        Color.blue
        ControlView(height: height, action: {})
    }
    .dynamicOverlay(Color.red)
    .dynamicOverlayBehavior(myOverlayBehavior)
}

var myOverlayBehavior: some DynamicOverlayBehavior {
    MagneticNotchOverlayBehavior<Notch> { ... }
    .onTranslation { translation in
        height = translation.height
    }
}
```
You can also be notified when a notch is reached using a binding:
```swift
@State var notch: Notch = .min

var body: some View {
    Color.blue
        .dynamicOverlay(Text("\(notch)"))
        .dynamicOverlayBehavior(myOverlayBehavior)
}

var myOverlayBehavior: some DynamicOverlayBehavior {
    MagneticNotchOverlayBehavior<Notch> { ... }
    .notchChange($notch)
}
```

### Moving the overlay

You can move explicitly the overlay using a notch binding.

```swift
@State var notch: Notch = .min

var body: some View {
    ZStack {
        Color.green
        Button("Move to top") {
            notch = .max
        }
    }
    .dynamicOverlay(Color.red)
    .dynamicOverlayBehavior(myOverlayBehavior)
}

var myOverlayBehavior: some DynamicOverlayBehavior {
    MagneticNotchOverlayBehavior<Notch> { ... }
    .notchChange($notch)
}
```
Wrap the change in an animation block to animate the change.

```swift
Button("Move to top") {
    withAnimation {
        notch = .max
    }
}
```

### Disabling notches

When a notch is disabled, the overlay will ignore it. Here we block the overlay in its `min` position:

```swift
@State var notch: Notch = .max

var myOverlayBehavior: some DynamicOverlayBehavior {
    MagneticNotchOverlayBehavior<Notch> { ... }
    .notchChange($notch)
    .disable(.max, notch == .min)
}
```

## Under the hood

`DynamicOverlay` is built on top of [OverlayContainer](https://github.com/applidium/OverlayContainer). If you need more control, consider using it or open an issue.

## Installation

`DynamicOverlay` is available through [CocoaPods](https://cocoapods.org). To install it, simply add the following line to your Podfile:

### Cocoapods

```ruby
pod 'DynamicOverlay'
```

### Carthage

Add the following to your Cartfile:

```ruby
github "https://github.com/fabernovel/DynamicOverlay"
```

### Swift Package Manager

`DynamicOverlay` can be installed as a Swift Package with Xcode 11 or higher. To install it, add a package using Xcode or a dependency to your Package.swift file:

```swift
.package(url: "https://github.com/fabernovel/DynamicOverlay.git")
```

## Release

- Create a release branch for the new version (release/#version#)
- Update the [CHANGELOG.md](https://github.com/faberNovel/DynamicOverlay/blob/main/CHANGELOG.md) (Be sure to spell your release version correctly)
- Push your release branch
- Run the [release workflow](https://github.com/faberNovel/DynamicOverlay/actions/workflows/release.yml) from your release branch

## Author

[@gaetanzanella](https://twitter.com/gaetanzanella), gaetan.zanella@fabernovel.com

## License

`DynamicOverlay` is available under the MIT license. See the LICENSE file for more info.


================================================
FILE: Source/DynamicOverlay.h
================================================
//
//  DynamicOverlay.h
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 05/03/2019.
//  Copyright © 2019 Fabernovel. All rights reserved.
//

#import <UIKit/UIKit.h>

//! Project version number for DynamicOverlay.
FOUNDATION_EXPORT double DynamicOverlayVersionNumber;

//! Project version string for DynamicOverlay.
FOUNDATION_EXPORT const unsigned char DynamicOverlayVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <DynamicOverlay/PublicHeader.h>




================================================
FILE: Source/Info.plist
================================================
<?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>FMWK</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0.0</string>
	<key>CFBundleVersion</key>
	<string>13</string>
</dict>
</plist>


================================================
FILE: Source/Internal/DynamicOverlayBehaviorValue.swift
================================================
//
//  EmptyFile.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 05/03/2019.
//  Copyright © 2019 Fabernovel. All rights reserved.
//

import SwiftUI

struct OverlayTranslation {
    let height: CGFloat
    let transaction: Transaction
    let isDragging: Bool
    let translationProgress: CGFloat
    let containerFrame: CGRect
    let velocity: CGPoint
    let heightForNotchIndex: (Int) -> CGFloat
}

struct DynamicOverlayBehaviorValue {

    let notchDimensions: [Int: NotchDimension]?
    let block: ((OverlayTranslation) -> Void)?
    let binding: Binding<Int>?
    let disabledNotchIndexes: Set<Int>

    init(notchDimensions: [Int: NotchDimension]? = nil,
         block: ((OverlayTranslation) -> Void)? = nil,
         binding: Binding<Int>? = nil,
         disabledNotchIndexes: Set<Int> = []) {
        self.notchDimensions = notchDimensions
        self.block = block
        self.binding = binding
        self.disabledNotchIndexes = disabledNotchIndexes
    }
}

extension DynamicOverlayBehaviorValue {

    static var `default`: DynamicOverlayBehaviorValue {
        DynamicOverlayBehaviorValue(
            notchDimensions: [
                0 : .fractional(0.3),
                1 : .fractional(0.5),
                2 : .fractional(0.7)
            ]
        )
    }
}

struct DynamicOverlayBehaviorKey: EnvironmentKey {

    static var defaultValue: DynamicOverlayBehaviorValue = .default
}

extension EnvironmentValues {

    var behaviorValue: DynamicOverlayBehaviorValue {
        set {
            self[DynamicOverlayBehaviorKey.self] = newValue
        }
        get {
            self[DynamicOverlayBehaviorKey.self]
        }
    }
}


================================================
FILE: Source/Internal/DynamicOverlayNotchTransition+DynamicOverlayBehavior.swift
================================================
//
//  MagneticNotchOverlayBehavior+DynamicOverlayBehavior.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 02/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

extension NotchDimension {

    enum ValueType: Hashable {
        case absolute
        case fractional
    }
}

extension MagneticNotchOverlayBehavior {

    // MARK: - DynamicOverlayBehavior

    func buildValue() -> DynamicOverlayBehaviorValue {
        DynamicOverlayBehaviorValue(
            notchDimensions: Dictionary(
                uniqueKeysWithValues: Notch.allCases.enumerated().map { i, notch in (i, value.dimensions(notch)) }
            ),
            block: value.translationBlocks.isEmpty ? nil : { translation in
                value.translationBlocks.forEach {
                    $0(
                        Translation(
                            height: translation.height,
                            transaction: translation.transaction,
                            progress: Double(min(max(translation.translationProgress, 0), 1)),
                            containerSize: translation.containerFrame.size,
                            heightForNotch: { notch in
                                translation.heightForNotchIndex(Notch.index(of: notch))
                            }
                        )
                    )
                }
            },
            binding: value.binding?.indexBinding(),
            disabledNotchIndexes: Set(value.disabledNotches.map { Notch.index(of: $0) })
        )
    }
}


================================================
FILE: Source/Internal/Handle/ActivatedOverlayArea.swift
================================================
//
//  ActivatedOverlayArea.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 04/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

struct ActivatedOverlayArea: Equatable {

    private struct Spot: Equatable {
        let frame: CGRect
    }

    private var spots: [Spot]

    mutating func merge(_ handle: ActivatedOverlayArea) {
        spots += handle.spots
    }

    var isEmpty: Bool {
        spots.isEmpty
    }

    func contains(_ rect: CGRect) -> Bool {
        spots.contains { $0.frame == rect }
    }

    func contains(_ point: CGPoint) -> Bool {
        spots.contains { $0.frame.contains(point) }
    }

    func intersects(_ rect: CGRect) -> Bool {
        spots.contains {
            // (gz) 2022-01-29 `SwiftUI` rounds the `UIKit` view frames.
            // A 0.25pt-width `SwiftUI` view can contain a 0.5pt-width `UIView`.
            rect.intersection($0.frame).width >= 0.5
            && $0.frame != .zero
        }
    }
}

extension ActivatedOverlayArea {

    static func active(_ frame: CGRect) -> ActivatedOverlayArea {
        ActivatedOverlayArea(spots: [Spot(frame: frame)])
    }

    static func inactive() -> ActivatedOverlayArea {
        ActivatedOverlayArea(spots: [Spot(frame: .zero)])
    }

    static var `default`: ActivatedOverlayArea {
        .empty
    }

    static var empty: ActivatedOverlayArea {
        ActivatedOverlayArea(spots: [])
    }
}


================================================
FILE: Source/Internal/Handle/ActiveOverlayAreaViewModifier.swift
================================================
//
//  ActiveOverlayAreaViewModifier.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 28/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

struct ActiveOverlayAreaViewModifier<Key: PreferenceKey>: ViewModifier where Key.Value == ActivatedOverlayArea {

    let key: Key.Type
    let isActive: Bool

    func body(content: Content) -> some View {
        content.background(
            GeometryReader { proxy in
                Spacer().preference(
                    key: key,
                    value: isActive ? .active(proxy.frame(in: .overlay)) : .inactive()
                )
            }
        )
    }
}


================================================
FILE: Source/Internal/Handle/Drag/DynamicOverlayDragArea.swift
================================================
//
//  DynamicOverlayDragArea.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 29/01/2022.
//  Copyright © 2022 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI

struct DynamicOverlayDragArea: Equatable {

    private let area: ActivatedOverlayArea

    init(area: ActivatedOverlayArea) {
        self.area = area
    }

    static var `default`: DynamicOverlayDragArea {
        DynamicOverlayDragArea(area: .default)
    }

    var isEmpty: Bool {
        area.isEmpty
    }

    func contains(_ rect: CGRect) -> Bool {
        area.contains(rect)
    }

    func contains(_ point: CGPoint) -> Bool {
        return area.contains(point)
    }
}

struct DynamicOverlayDragAreaPreferenceKey: PreferenceKey {

    typealias Value = ActivatedOverlayArea

    static var defaultValue: ActivatedOverlayArea = .default

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value.merge(nextValue())
    }
}


================================================
FILE: Source/Internal/Handle/Drag/OnDragAreaChangeViewModifier.swift
================================================
//
//  OnDragAreaChangeViewModifier.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI

private struct OnDragAreaChangeViewModifier: ViewModifier {

    let handler: (DynamicOverlayDragArea) -> Void

    func body(content: Content) -> some View {
        content.onPreferenceChange(DynamicOverlayDragAreaPreferenceKey.self) { area in
            handler(DynamicOverlayDragArea(area: area))
        }
    }
}

extension View {

    func onDragAreaChange(handler: @escaping (DynamicOverlayDragArea) -> Void) -> some View {
        modifier(OnDragAreaChangeViewModifier(handler: handler))
    }
}


================================================
FILE: Source/Internal/Handle/DrivingScrollView/DynamicOverlayScrollViewProxy.swift
================================================
//
//  DynamicOverlayScrollViewProxyPreferenceKey.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 11/01/2022.
//  Copyright © 2022 Fabernovel. All rights reserved.
//

import SwiftUI

struct DynamicOverlayScrollViewProxy: Equatable {

    private let area: ActivatedOverlayArea

    init(area: ActivatedOverlayArea) {
        self.area = area
    }

    static var `default`: DynamicOverlayScrollViewProxy {
        DynamicOverlayScrollViewProxy(area: .default)
    }

    func findScrollView(in space: UIView) -> UIScrollView? {
        space.findScrollView(in: area, coordinate: space)
    }
}


struct DynamicOverlayScrollViewProxyPreferenceKey: PreferenceKey {

    typealias Value = ActivatedOverlayArea

    static var defaultValue: ActivatedOverlayArea = .default

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value.merge(nextValue())
    }
}

private extension UIView {

    func findScrollView(in area: ActivatedOverlayArea,
                        coordinate: UICoordinateSpace) -> UIScrollView? {
        let frame = coordinate.convert(bounds, from: self)
        guard area.intersects(frame) else { return nil }
        if let result = self as? UIScrollView {
            return result
        }
        for subview in subviews {
            if let result = subview.findScrollView(in: area, coordinate: coordinate) {
                return result
            }
        }
        return nil
    }
}


================================================
FILE: Source/Internal/Handle/DrivingScrollView/OnDrivingScrollViewChangeViewModifier.swift
================================================
//
//  OnDrivingScrollViewChangeViewModifier.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import SwiftUI

private struct OnDrivingScrollViewChangeViewModifier: ViewModifier {

    let handler: (DynamicOverlayScrollViewProxy) -> Void

    func body(content: Content) -> some View {
        content.onPreferenceChange(DynamicOverlayScrollViewProxyPreferenceKey.self, perform: { value in
            handler(DynamicOverlayScrollViewProxy(area: value))
        })
    }
}

extension View {

    func onDrivingScrollViewChange(handler: @escaping (DynamicOverlayScrollViewProxy) -> Void) -> some View {
        modifier(OnDrivingScrollViewChangeViewModifier(handler: handler))
    }
}


================================================
FILE: Source/Internal/Handle/OverlayContainerCoordinateSpace.swift
================================================
//
//  OverlayContainer+CoordinateSpace.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 28/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

extension CoordinateSpace {

    static var overlay: CoordinateSpace {
        .named("Overlay")
    }
}

extension View {

    func overlayCoordinateSpace() -> some View {
        modifier(OverlayCoordinateSpaceViewModifier())
    }
}

private struct OverlayCoordinateSpaceViewModifier: ViewModifier {

    func body(content: Content) -> some View {
        content.coordinateSpace(name: "Overlay")
    }
}


================================================
FILE: Source/Internal/MagneticNotchOverlayBehaviorValue.swift
================================================
//
//  MagneticNotchOverlayBehaviorValue.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 02/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

extension MagneticNotchOverlayBehavior {

    struct Value {

        let dimensions: (Notch) -> NotchDimension
        let translationBlocks: [(Translation) -> Void]
        let binding: Binding<Notch>?
        let disabledNotches: [Notch]

        init(dimensions: @escaping (Notch) -> NotchDimension,
             translationBlocks: [(Translation) -> Void],
             binding: Binding<Notch>?,
             disabledNotches: [Notch]) {
            self.dimensions = dimensions
            self.translationBlocks = translationBlocks
            self.binding = binding
            self.disabledNotches = disabledNotches
        }

        init(dimensions: @escaping (Notch) -> NotchDimension) {
            self.dimensions = dimensions
            self.translationBlocks = []
            self.binding = nil
            self.disabledNotches = []
        }

        // MARK: - Public

        func appending(_ block: @escaping (Translation) -> Void) -> Self {
            Value(
                dimensions: dimensions,
                translationBlocks: translationBlocks + [block],
                binding: binding,
                disabledNotches: disabledNotches
            )
        }

        func setting(_ binding: Binding<Notch>) -> Self {
            Value(
                dimensions: dimensions,
                translationBlocks: translationBlocks,
                binding: binding,
                disabledNotches: disabledNotches
            )
        }

        func disabling(_ isDisabled: Bool, _ notch: Notch) -> Self {
            Value(
                dimensions: dimensions,
                translationBlocks: translationBlocks,
                binding: binding,
                disabledNotches: isDisabled ? disabledNotches + [notch] : disabledNotches
            )
        }
    }
}


================================================
FILE: Source/Internal/OverlayContainer/DynamicOverlayContainerAnimationController.swift
================================================
//
//  DynamicOverlayContainerAnimationController.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 28/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import UIKit
import SwiftUI
import OverlayContainer

private struct Constant {
    static let defaultMass: CGFloat = 1
    static let defaultDamping: CGFloat = 0.7
    static let defaultRigidDamping: CGFloat = 0.9
    static let defaultResponse: CGFloat = 0.3
    static let minimumDamping: CGFloat = 1
    static let minimumVelocityConsideration: CGFloat = 150
    static let maximumVelocityConsideration: CGFloat = 3000
}

struct DynamicOverlayContainerAnimationController: OverlayAnimatedTransitioning {

    private var mass: CGFloat = Constant.defaultMass
    private var damping: CGFloat = Constant.defaultDamping
    private var response: CGFloat = Constant.defaultResponse

    // MARK: - Life Cycle

    public init(style: OverlayContainerViewController.OverlayStyle) {
        switch style {
        case .expandableHeight, .rigid:
            // (gz) 2019-06-15 We also nullify the damping value when using rigid styles
            // to avoid the panel to be lifted above the bottom of the screen.
            damping = Constant.defaultRigidDamping
        case .flexibleHeight:
            damping = Constant.defaultDamping
        }
    }

    // MARK: - Public

    public func animation(using context: OverlayContainerTransitionCoordinatorContext) -> Animation? {
        guard context.isAnimated else { return nil }
        return .interpolatingSpring(
            mass: Double(springMass(context: context)),
            stiffness: Double(springStiffness(context: context)),
            damping: Double(springDamping(context: context)),
            initialVelocity: Double(springVelocity(context: context))
        )
    }

    // MARK: - OverlayAnimatedTransitioning

    public func interruptibleAnimator(using context: OverlayContainerContextTransitioning) -> UIViewImplicitlyAnimating {
        let timing = UISpringTimingParameters(
            mass: springMass(context: context),
            stiffness: springStiffness(context: context),
            damping: springDamping(context: context),
            initialVelocity: CGVector(dx: springVelocity(context: context), dy: springVelocity(context: context))
        )
        return UIViewPropertyAnimator(
            duration: 0, // duration is ignored when using `UISpringTimingParameters.init(mass:stiffness:damping:initialVelocity)`
            timingParameters: timing
        )
    }

    private func springMass(context: OverlayContainerTransitionContext) -> CGFloat {
        mass
    }

    private func springStiffness(context: OverlayContainerTransitionContext) -> CGFloat {
        pow(2 * .pi / response, 2)
    }

    private func springDamping(context: OverlayContainerTransitionContext) -> CGFloat {
        let velocity = min(
            Constant.maximumVelocityConsideration,
            max(abs(context.velocity.y), Constant.minimumVelocityConsideration)
        )
        let velocityRange = Constant.maximumVelocityConsideration - Constant.minimumVelocityConsideration
        let normalizedVelocity = (velocity - Constant.minimumVelocityConsideration) / velocityRange
        let normalizedDamping = normalizedVelocity * (damping - Constant.minimumDamping) + Constant.minimumDamping
        return 4 * .pi * normalizedDamping / response
    }

    private func springVelocity(context: OverlayContainerTransitionContext) -> CGFloat {
        0
    }
}


================================================
FILE: Source/Internal/OverlayContainer/OverlayContainerCoordinator.swift
================================================
//
//  OverlayContainerCoordinator.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 02/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import UIKit
import SwiftUI
import OverlayContainer

struct OverlayContainerLayout: Equatable {
    let indexToDimension: [Int: NotchDimension]
}

// (gz) 2022-01-30 `SwiftUI` compares struct properties one by one to determine either to update the view or not.
// To avoid useless updates, we wrap the passive values inside this class.
class OverlayContainerPassiveContainer: Equatable {

    var onTranslation: ((OverlayTranslation) -> Void)?
    var onNotchChange: ((Int) -> Void)?

    static func == (lhs: OverlayContainerPassiveContainer, rhs: OverlayContainerPassiveContainer) -> Bool {
        lhs === rhs
    }
}

struct OverlayContainerState: Equatable {
    let dragArea: DynamicOverlayDragArea
    let drivingScrollViewProxy: DynamicOverlayScrollViewProxy
    let notchIndex: Int?
    let disabledNotches: Set<Int>
    let layout: OverlayContainerLayout
}

class OverlayContainerCoordinator {

    private let background: UIViewController
    private let content: UIViewController

    private let indexMapper = OverlayNotchIndexMapper()

    typealias State = OverlayContainerState

    private var state: State
    private let style: OverlayContainerViewController.OverlayStyle
    private let passiveContainer: OverlayContainerPassiveContainer

    private var animationController: DynamicOverlayContainerAnimationController {
        DynamicOverlayContainerAnimationController(style: style)
    }

    // MARK: - Life Cycle

    init(style: OverlayContainerViewController.OverlayStyle,
         layout: OverlayContainerLayout,
         passiveContainer: OverlayContainerPassiveContainer,
         background: UIViewController,
         content: UIViewController) {
        self.state = .initial(layout)
        self.passiveContainer = passiveContainer
        self.background = background
        self.content = content
        self.style = style
    }

    // MARK: - Public

    func move(_ container: OverlayContainerViewController, to state: State, animated: Bool) {
        if container.viewControllers.isEmpty {
            container.viewControllers = [background, content]
        }
        let changes = OverlayContainerStateDiffer().diff(
            from: self.state,
            to: state
        )
        let requiresLayoutUpdate = changes.contains(.index) || changes.contains(.layout)
        if requiresLayoutUpdate && animated {
            // we update the content first
            container.drivingScrollView = nil // issue #21
            container.view.layoutIfNeeded()
        }
        if changes.contains(.layout) {
            container.invalidateNotchHeights()
        }
        if let index = state.notchIndex, changes.contains(.index) {
            container.moveOverlay(toNotchAt: index, animated: animated)
        }
        if changes.contains(.scrollView) {
            CATransaction.setCompletionBlock { [weak container] in
                guard let overlay = container?.topViewController?.view else { return }
                container?.drivingScrollView = state.drivingScrollViewProxy.findScrollView(in: overlay)
            }
        }
        self.state = state
        if changes.contains(.layout) && !animated {
            UIView.performWithoutAnimation {
                container.view.layoutIfNeeded()
            }
        }
    }
}

extension OverlayContainerCoordinator: OverlayContainerViewControllerDelegate {

    // MARK: - OverlayContainerViewControllerDelegate

    func numberOfNotches(in containerViewController: OverlayContainerViewController) -> Int {
        indexMapper.reload(
            layout: state.layout,
            availableHeight: containerViewController.availableSpace
        )
        return indexMapper.numberOfOverlayIndexes()
    }

    func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                        heightForNotchAt index: Int,
                                        availableSpace: CGFloat) -> CGFloat {
        indexMapper.height(forOverlayIndex: index)
    }

    func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                        didMoveOverlay overlayViewController: UIViewController,
                                        toNotchAt index: Int) {
        let newState = state.withNewNotch(index)
        guard newState != state else { return }
        passiveContainer.onNotchChange?(indexMapper.dynamicIndex(forOverlayIndex: index))
    }

    func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                        willTranslateOverlay overlayViewController: UIViewController,
                                        transitionCoordinator: OverlayContainerTransitionCoordinator) {
        let animation = animationController.animation(using: transitionCoordinator)
        let transaction = Transaction(animation: animation)
        let translation = OverlayTranslation(
            height: transitionCoordinator.targetTranslationHeight,
            transaction: transaction,
            isDragging: transitionCoordinator.isDragging,
            translationProgress: transitionCoordinator.overallTranslationProgress(),
            containerFrame: containerViewController.view.frame,
            velocity: transitionCoordinator.velocity,
            heightForNotchIndex: { transitionCoordinator.height(forNotchAt: $0) }
        )
        withTransaction(transaction) { [weak passiveContainer] in
            passiveContainer?.onTranslation?(translation)
        }
    }

    func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                        canReachNotchAt index: Int,
                                        forOverlay overlayViewController: UIViewController) -> Bool {
        !state.disabledNotches.map { indexMapper.overlayIndex(forDynamicIndex: $0) }.contains(index)
    }

    func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                        shouldStartDraggingOverlay overlayViewController: UIViewController,
                                        at point: CGPoint,
                                        in coordinateSpace: UICoordinateSpace) -> Bool {
        guard let overlay = containerViewController.topViewController else { return false }
        let inOverlayPoint = overlay.view.convert(point, from: coordinateSpace)
        if state.dragArea.isEmpty {
            return overlay.view.frame.contains(inOverlayPoint)
        }
        return state.dragArea.contains(inOverlayPoint)
    }

    func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                        transitioningDelegateForOverlay overlayViewController: UIViewController) -> OverlayTransitioningDelegate? {
        self
    }
}

extension OverlayContainerCoordinator: OverlayTransitioningDelegate {

    // MARK: - OverlayTransitioningDelegate

    func animationController(for overlayViewController: UIViewController) -> OverlayAnimatedTransitioning? {
        animationController
    }
}

private extension OverlayContainerState {

    static func initial(_ layout: OverlayContainerLayout) -> OverlayContainerState {
        OverlayContainerState(
            dragArea: .default,
            drivingScrollViewProxy: .default,
            notchIndex: nil,
            disabledNotches: [],
            layout: layout
        )
    }

    func withNewNotch(_ notch: Int) -> OverlayContainerState {
        OverlayContainerState(
            dragArea: dragArea,
            drivingScrollViewProxy: drivingScrollViewProxy,
            notchIndex: notch,
            disabledNotches: disabledNotches,
            layout: layout
        )
    }
}


================================================
FILE: Source/Internal/OverlayContainer/OverlayContainerDynamicOverlayView.swift
================================================
//
//  OverlayContainerView.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 02/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

struct OverlayContainerDynamicOverlayView<Background: View, Content: View>: View {

    @State
    private var dragArea: DynamicOverlayDragArea = .default

    @State
    private var scrollViewProxy: DynamicOverlayScrollViewProxy = .default

    @State
    private var passiveContainer = OverlayContainerPassiveContainer()

    @Environment(\.behaviorValue)
    private var behavior: DynamicOverlayBehaviorValue

    let background: Background
    let content: Content

    // MARK: - View

    var body: some View {
        SwiftUIOverlayContainerRepresentableAdaptor(
            adaptor: OverlayContainerRepresentableAdaptor(
                containerState: makeContainerState(),
                passiveContainer: passiveContainer,
                content: OverlayContentHostingView(),
                background: background
            )
        )
        .overlayContent(content.overlayCoordinateSpace())
        .onUpdate {
            passiveContainer.onTranslation = behavior.block
            // This is tricky. `OverlayContainerPassiveContainer` is a class inside a struct,
            // `passiveContainer.onNotchChange = { self.behavior.binding?.wrappedValue = $0 }`
            // would create a retain cycle as `self` includes a ref to `passiveContainer`.
            let behavior = behavior
            passiveContainer.onNotchChange = { behavior.binding?.wrappedValue = $0 }
        }
        .onDragAreaChange {
            dragArea = $0
        }
        .onDrivingScrollViewChange {
            scrollViewProxy = $0
        }
    }

    // MARK: - Private

    private func makeContainerState() -> OverlayContainerState {
        OverlayContainerState(
            dragArea: dragArea,
            drivingScrollViewProxy: scrollViewProxy,
            notchIndex: behavior.binding?.wrappedValue,
            disabledNotches: behavior.disabledNotchIndexes,
            layout: OverlayContainerLayout(indexToDimension: behavior.notchDimensions ?? [:])
        )
    }
}

private extension View {

    func onUpdate(_ block: () -> Void) -> some View {
        block()
        return self
    }
}


================================================
FILE: Source/Internal/OverlayContainer/OverlayContainerRepresentableAdaptor.swift
================================================
//
//  OverlayContainerRepresentableAdaptor.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 20/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI
import OverlayContainer

struct OverlayContainerRepresentableAdaptor<Content: View, Background: View> {

    struct Context {
        let coordinator: OverlayContainerCoordinator
        let transaction: Transaction
    }

    let containerState: OverlayContainerState
    let passiveContainer: OverlayContainerPassiveContainer
    let content: Content
    let background: Background

    private let style: OverlayContainerViewController.OverlayStyle = .expandableHeight

    // MARK: - UIViewControllerRepresentable

    func makeCoordinator() -> OverlayContainerCoordinator {
        let contentController = UIHostingController(rootView: content)
        contentController.view.backgroundColor = .clear
        contentController.view.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
        contentController.view.setContentHuggingPriority(.defaultLow, for: .vertical)
        let backgroundController = UIHostingController(rootView: background)
        backgroundController.view.backgroundColor = .clear
        return OverlayContainerCoordinator(
            style: style,
            layout: containerState.layout,
            passiveContainer: passiveContainer,
            background: backgroundController,
            content: contentController
        )
    }

    func makeUIViewController(context: Context) -> OverlayContainerViewController {
        let controller = OverlayContainerViewController(style: style)
        controller.delegate = context.coordinator
        return controller
    }

    func updateUIViewController(_ container: OverlayContainerViewController,
                                context: Context) {
        context.coordinator.move(
            container,
            to: containerState,
            animated: context.transaction.animation != nil
        )
    }
}


================================================
FILE: Source/Internal/OverlayContainer/OverlayContainerStateDiffer.swift
================================================
//
//  OverlayContainerStateDiffer.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 23/07/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation

struct OverlayContainerStateDiffer {

    struct Changes: OptionSet {
        let rawValue: Int

        static let layout = Changes(rawValue: 1 << 0)
        static let index = Changes(rawValue: 1 << 1)
        static let scrollView = Changes(rawValue: 1 << 2)
    }

    func diff(from previous: OverlayContainerState, to next: OverlayContainerState) -> Changes {
        var changes: Changes = []
        if previous.notchIndex != next.notchIndex {
            changes.insert(.index)
        }
        // issue #21
        // The scroll view depends on the content, we need to first for a potential new scroll view
        // at each update
        changes.insert(.scrollView)
        if previous.layout != next.layout {
            changes.insert(.layout)
        }
        return changes
    }
}


================================================
FILE: Source/Internal/OverlayContainer/SwiftUIOverlayContainerRepresentableAdaptor.swift
================================================
//
//  SwiftUIOverlayContainerRepresentableAdaptor.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI
import OverlayContainer

struct SwiftUIOverlayContainerRepresentableAdaptor<Content: View, Background: View>: UIViewControllerRepresentable {

    let adaptor: OverlayContainerRepresentableAdaptor<Content, Background>

    // MARK: - UIViewControllerRepresentable

    func makeCoordinator() -> OverlayContainerCoordinator {
        adaptor.makeCoordinator()
    }

    func makeUIViewController(context: Context) -> OverlayContainerViewController {
        adaptor.makeUIViewController(context: map(context))
    }

    func updateUIViewController(_ uiViewController: OverlayContainerViewController, context: Context) {
        adaptor.updateUIViewController(uiViewController, context: map(context))
    }

    private func map(_ context: Context) -> OverlayContainerRepresentableAdaptor<Content, Background>.Context {
        OverlayContainerRepresentableAdaptor<Content, Background>.Context(
            coordinator: context.coordinator,
            transaction: context.transaction
        )
    }
}


================================================
FILE: Source/Internal/OverlayContentModifier.swift
================================================
//
//  OverlayContentModifier.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 03/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

struct OverlayContentModifier<Overlay: View>: ViewModifier {

    let overlay: Overlay

    func body(content: Content) -> some View {
        content.environment(\.overlayContentKey, AnyView(overlay))
    }
}

extension View {

    func overlayContent<Content: View>(_ content: Content) -> ModifiedContent<Self, OverlayContentModifier<Content>> {
        modifier(OverlayContentModifier(overlay: content))
    }
}

/// The root view of the overlay content
struct OverlayContentHostingView: View {

    /// We use an environment variable to avoid UIViewController allocations each time the content changes.
    @Environment(\.overlayContentKey)
    var content: AnyView

    var body: some View {
        content
    }
}

private struct OverlayContentKey: EnvironmentKey {

    static var defaultValue = AnyView(EmptyView())
}

private extension EnvironmentValues {

    var overlayContentKey: AnyView {
        set {
            self[OverlayContentKey.self] = newValue
        }
        get {
            self[OverlayContentKey.self]
        }
    }
}


================================================
FILE: Source/Internal/OverlayNotchIndexMapper.swift
================================================
//
//  OverlayNotchIndexMapper.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 29/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

class OverlayNotchIndexMapper {

    private var overlayIndexToDynamicIndex: [Int: Int] = [:]
    private var overlayIndexToHeight: [Int: CGFloat] = [:]
    private var dynamicIndexToOverlayIndex: [Int: Int] = [:]

    func reload(layout: OverlayContainerLayout, availableHeight: CGFloat) {
        overlayIndexToDynamicIndex = [:]
        overlayIndexToHeight = [:]
        dynamicIndexToOverlayIndex = [:]
        let sortedIndexes = layout.indexToDimension.sorted(by: {
            height(for: $0.value, availableHeight: availableHeight) < height(for: $1.value, availableHeight: availableHeight)
        })
        sortedIndexes.enumerated().forEach{ overlayIndex, dynamicValue in
            overlayIndexToHeight[overlayIndex] = height(for: dynamicValue.value, availableHeight: availableHeight)
            dynamicIndexToOverlayIndex[dynamicValue.key] = overlayIndex
            overlayIndexToDynamicIndex[overlayIndex] = dynamicValue.key
        }
    }

    func numberOfOverlayIndexes() -> Int {
        overlayIndexToDynamicIndex.count
    }

    func dynamicIndex(forOverlayIndex index: Int) -> Int {
        dynamicIndexToOverlayIndex[index] ?? 0
    }

    func overlayIndex(forDynamicIndex index: Int) -> Int {
        overlayIndexToDynamicIndex[index] ?? 0
    }

    func height(forOverlayIndex index: Int) -> CGFloat {
        overlayIndexToHeight[index] ?? 0
    }

    private func height(for dimension: NotchDimension,
                        availableHeight: CGFloat) -> CGFloat {
        switch dimension.type {
        case .absolute:
            return CGFloat(dimension.value)
        case .fractional:
            return availableHeight * CGFloat(dimension.value)
        }
    }
}


================================================
FILE: Source/Internal/Utils/Binding+CaseIterable.swift
================================================
//
//  Binding+CaseIterable.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 02/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

extension Binding where Value: Equatable, Value: CaseIterable {

    func indexBinding() -> Binding<Int> {
        Binding<Int>(
            get: {
                Value.index(of: wrappedValue)
            },
            set: { index in
                wrappedValue = Value.value(at: index)
            }
        )
    }
}

extension CaseIterable where Self: Equatable {

    static func index(of target: AllCases.Element) -> Int {
        var index = 0
        for value in allCases {
            if value == target {
                return index
            }
            index += 1
        }
        fatalError("Cannot find a valid index for \(target)")
    }

    static func value(at index: Int) -> AllCases.Element {
        allCases[allCases.index(allCases.startIndex, offsetBy: index)]
    }
}


================================================
FILE: Source/Public/DynamicOverlayBehavior.swift
================================================
//
//  DynamicOverlayBehavior.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 02/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

/// A protocol that describes the overlay behavior.
public protocol DynamicOverlayBehavior {

    func makeModifier() -> AddDynamicOverlayBehaviorModifier
}


================================================
FILE: Source/Public/DynamicOverlayHandle.swift
================================================
//
//  DynamicOverlayHandle.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 04/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

public extension View {

    /// Defines the target as a draggable view.
    ///
    /// - parameter isActive: Boolean indicating whether the target is draggable.
    func draggable(_ isActive: Bool = true) -> some View {
        modifier(
            ActiveOverlayAreaViewModifier(
                key: DynamicOverlayDragAreaPreferenceKey.self,
                isActive: isActive
            )
        )
    }

    /// Defines the target as the container of a driving scroll view.
    /// When specified a driving scroll view coordinates its scrolling with the overlay translation.
    ///
    /// - parameter isActive: Boolean indicating whether the scroll view is active.
    func drivingScrollView(_ isActive: Bool = true) -> some View {
        modifier(
            ActiveOverlayAreaViewModifier(
                key: DynamicOverlayScrollViewProxyPreferenceKey.self,
                isActive: isActive
            )
        )
    }
}


================================================
FILE: Source/Public/DynamicOverlayModifier.swift
================================================
//
//  DynamicOverlayModifier.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 01/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

public extension View {

    /// Adds a dynamic overlay above this view.
    ///
    /// - parameter content: the content of the overlay.
    ///
    /// - returns: A view with a dynamic overlay added above this view.
    func dynamicOverlay<Content: View>(_ content: Content) -> some View {
        modifier(AddDynamicOverlayModifier(overlay: content))
    }

    /// Sets the overlay behavior for dynamic overlays within this view.
    ///
    /// - parameter behavior: the behavior to apply.
    ///
    /// - returns: A view with the specified behavior set.
    /// 
    /// This modifier affects the given view, as well as that view’s descendant views. It has no effect outside the view hierarchy on which you call it.
    func dynamicOverlayBehavior<Behavior: DynamicOverlayBehavior>(_ behavior: Behavior) -> some View {
        modifier(behavior.makeModifier())
    }
}

public struct AddDynamicOverlayModifier<Overlay: View>: ViewModifier {

    let overlay: Overlay

    // MARK: - ViewModifier

    public func body(content: Content) -> some View {
        OverlayContainerDynamicOverlayView(
            background: content,
            content: overlay
        )
    }
}

public struct AddDynamicOverlayBehaviorModifier: ViewModifier {

    let value: DynamicOverlayBehaviorValue

    // MARK: - ViewModifier

    public func body(content: Content) -> some View {
        content.environment(\.behaviorValue, value)
    }
}


================================================
FILE: Source/Public/MagneticNotchOverlayBehavior.swift
================================================
//
//  MagneticNotchOverlayBehavior.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 02/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import SwiftUI

/// A `DynamicOverlayBehavior` instance describing an overlay that can be dragged up and down alongside predefined notches.
/// Whenever a drag gesture ends, the overlay motion will continue until it reaches one of its notches.
public struct MagneticNotchOverlayBehavior<Notch> where Notch: CaseIterable, Notch: Equatable {

    let value: Value

    /// Creates a behavior with the given notches.
    ///
    /// - parameter notches: The notches.
    ///
    /// - returns: A behavior with the specified notches.
    public init(notches: @escaping (Notch) -> NotchDimension) {
        self.value = Value(dimensions: notches)
    }

    init(value: Value) {
        self.value = value
    }
}

public extension MagneticNotchOverlayBehavior {

    /// The attributes of a translation
    struct Translation {
        /// The current overlay height.
        public let height: CGFloat
        /// The transaction associated to the translation.
        public let transaction: Transaction
        /// The overlay translation progress (from 0.0 to 1.0).
        public let progress: Double
        /// The overlay container size.
        public let containerSize: CGSize

        let heightForNotch: (Notch) -> CGFloat

        /// returns the height of the given notch.
        public func height(for notch: Notch) -> CGFloat {
            heightForNotch(notch)
        }
    }

    /// Adds an action to perform when the overlay moves.
    ///
    /// - parameter action: The action to perform when the translation changes. The action closure’s parameter contains the current translation.
    ///
    /// - returns: A version of the behavior that triggers the action when the translation changes.
    func onTranslation(_ action: @escaping (Translation) -> Void) -> Self {
        MagneticNotchOverlayBehavior(value: value.appending(action))
    }
}

public extension MagneticNotchOverlayBehavior {

    /// Updates the current overlay notch as it changes.
    ///
    /// - parameter binding: A binding to a notch property.
    ///
    /// - returns: A version of the behavior that updates the current overlay notch as it changes.
    func notchChange(_ binding: Binding<Notch>) -> Self {
        MagneticNotchOverlayBehavior(value: value.setting(binding))
    }
}

public extension MagneticNotchOverlayBehavior {

    /// Disables a notch.
    ///
    /// - parameter notch: The notch to disable.
    /// - parameter isDisabled: A boolean indicating whether the notch should be disabled.
    ///
    /// - returns: A version of the behavior that disables the specified notch.
    ///
    /// When a notch is disabled the overlay can not be dragged to it.
    /// The `notchChange` binding is still effective though.
    func disable(_ notch: Notch, _ isDisabled: Bool = true) -> Self {
        MagneticNotchOverlayBehavior(value: value.disabling(isDisabled, notch))
    }
}

extension MagneticNotchOverlayBehavior: DynamicOverlayBehavior {

    public func makeModifier() -> AddDynamicOverlayBehaviorModifier {
        AddDynamicOverlayBehaviorModifier(value: buildValue())
    }
}

/// A dimension of a notch.
public struct NotchDimension: Hashable {

    let type: ValueType
    let value: Double
}

public extension NotchDimension {

    /// Creates a dimension with an absolute point value.
    static func absolute(_ value: Double) -> NotchDimension {
        NotchDimension(type: .absolute, value: value)
    }

    /// Creates a dimension that is computed as a fraction of the height of the overlay parent view.
    static func fractional(_ value: Double) -> NotchDimension {
        NotchDimension(type: .fractional, value: value)
    }
}


================================================
FILE: Tests/DynamicOverlayTests/DragHandleViewModifierTests.swift
================================================
//
//  DragHandleTests.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import XCTest
import SwiftUI
@testable import DynamicOverlay

private struct HandleView: View {

    var body: some View {
        Color.red
    }
}

private struct ContainerView: View {

    let isActive: Bool
    let frame: CGRect
    let handler: (DynamicOverlayDragArea) -> Void

    var body: some View {
        GeometryReader { _ in
            HandleView()
                .frame(width: frame.width, height: frame.height)
                .draggable(isActive)
                .offset(x: frame.origin.x, y: frame.origin.y)
        }
        .onDragAreaChange(handler: handler)
        .overlayCoordinateSpace()
    }
}

private struct MultipleHandlesView: View {

    let handler: (DynamicOverlayDragArea) -> Void

    var body: some View {
        VStack {
            Color.orange.draggable()
            Color.red.draggable()
        }
        .overlayCoordinateSpace()
        .onDragAreaChange(handler: handler)
    }
}

class DragHandleViewModifierTests: XCTestCase {

    func testActiveState() {
        let frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        let activeHandle = DynamicOverlayDragArea(area: .active(frame))
        let notActiveHandle = DynamicOverlayDragArea(area: .inactive())
        let point = CGPoint(x: 20.0, y: 20.0)
        XCTAssertTrue(activeHandle.contains(point))
        XCTAssertFalse(notActiveHandle.contains(point))
    }

    func testMultipleFrames() {
        let values: [(Bool, CGRect)] = [
            (true, CGRect(x: 30, y: 30, width: 50, height: 100)),
            (false, CGRect(x: 0, y: 0, width: 400, height: 400)),
            (true, CGRect(x: 0, y: 0, width: 400, height: 400)),
        ]
        values.forEach { isActive, frame in
            let expectation = XCTestExpectation()
            let view = ContainerView(
                isActive: isActive,
                frame: frame,
                handler: { handler in
                    XCTAssertEqual(handler.contains(frame), isActive)
                    expectation.fulfill()
                }
            )
            ViewRenderer(view: view).render()
            wait(for: [expectation], timeout: 0.3)
        }
    }
}


================================================
FILE: Tests/DynamicOverlayTests/DrivingScrollViewModifierTests.swift
================================================
//
//  DrivingScrollViewModifierTests.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import XCTest
import SwiftUI
@testable import DynamicOverlay

private struct ContainerView: View {

    let isActive: Bool
    let isActiveHandler: (DynamicOverlayScrollViewProxy) -> Void

    var body: some View {
        ScrollView {
            Color.green
        }
        .overlayCoordinateSpace()
        .drivingScrollView(isActive)
        .onDrivingScrollViewChange(handler: isActiveHandler)
    }
}

private class IdentifiedScrollView: UIScrollView {
    var id = ""
}

class DrivingScrollViewModifierTests: XCTestCase {

    func testScrollViewSearch() {
        let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
        let layer = UIView(frame: container.bounds)
        container.addSubview(layer)
        let scrollViews = Array(repeating: IdentifiedScrollView(), count: 4)
        for i in scrollViews.indices {
            let scrollView = scrollViews[i]
            scrollView.frame.size.height = container.bounds.height / 2
            scrollView.frame.size.width = container.bounds.width / 2
            scrollView.frame.origin.x = container.bounds.width / 2 * CGFloat(i % 2)
            scrollView.frame.origin.y = i > 1 ? container.bounds.height / 2 : 0
            if i.isMultiple(of: 2) {
                layer.addSubview(scrollView)
            } else {
                container.addSubview(scrollView)
            }
        }
        for scrollView in scrollViews {
            scrollViews.forEach { $0.id = "lure" }
            scrollView.id = "target"
            let proxy = DynamicOverlayScrollViewProxy(
                area: .active(scrollView.frame)
            )
            let scrollView = proxy.findScrollView(in: container) as! IdentifiedScrollView
            XCTAssertEqual(scrollView.id, "target")
        }
    }

    func testDrivingScrollView() {
        [false, true].forEach { shouldBeActive in
            let expectation = XCTestExpectation()
            var window: UIWindow!
            let view = ContainerView(
                isActive: shouldBeActive,
                isActiveHandler: { handle in
                    CATransaction.setCompletionBlock {
                        if shouldBeActive {
                            XCTAssertNotNil(handle.findScrollView(in: window))
                        } else {
                            XCTAssertNil(handle.findScrollView(in: window))
                        }
                        expectation.fulfill()
                    }
                }
            )
            let renderer = ViewRenderer(view: view)
            window = renderer.window
            renderer.render()
            wait(for: [expectation], timeout: 0.1)
        }
    }

    func testNoneDrivingScrollView() {
        let expectation = XCTestExpectation()
        expectation.isInverted = true
        let view = Color.red.onDrivingScrollViewChange { _ in
            expectation.fulfill()
        }
        ViewRenderer(view: view).render()
        wait(for: [expectation], timeout: 0.1)
    }
}


================================================
FILE: Tests/DynamicOverlayTests/MagneticNotchOverlayBehaviorValueTests.swift
================================================
//
//  MagneticNotchOverlayBehaviorValueTests.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import XCTest
import SwiftUI
@testable import DynamicOverlay

private enum Constant {

    static func dimension(for notch: TestNotch) -> NotchDimension {
        switch notch {
        case .min:
            return .fractional(0.3)
        case .max:
            return .fractional(0.5)
        }
    }
}

private enum TestNotch: CaseIterable, Equatable {
    case min, max
}

private typealias Behavior = MagneticNotchOverlayBehavior<TestNotch>

class MagneticNotchOverlayBehaviorValueTests: XCTestCase {

    func testDisabledIndexesOverlayValue() {
        var behavior = Behavior.empty()
        XCTAssertEqual(behavior.buildValue().disabledNotchIndexes, [])
        behavior = behavior.disable(.min)
        XCTAssertEqual(behavior.buildValue().disabledNotchIndexes, [0])
        behavior = behavior.disable(.max)
        XCTAssertEqual(behavior.buildValue().disabledNotchIndexes, [0, 1])
    }

    func testNotchChangeOverlayValue() {
        var behavior = Behavior.empty()
        XCTAssertTrue(behavior.buildValue().binding == nil)
        behavior = behavior.notchChange(.constant(.min))
        XCTAssertTrue(behavior.buildValue().binding?.wrappedValue == 0)
        behavior = behavior.notchChange(.constant(.max))
        XCTAssertTrue(behavior.buildValue().binding?.wrappedValue == 1)
    }

    func testNotchDimensionOverlayValue() {
        XCTAssertEqual(
            Behavior.empty().buildValue().notchDimensions,
            [
                0: Constant.dimension(for: .min),
                1: Constant.dimension(for: .max),
            ]
        )
    }

    func testBlockOverlayValue() {
        var behavior = Behavior.empty()
        XCTAssertTrue(behavior.buildValue().block == nil)
        behavior = behavior.onTranslation { _ in }
        XCTAssertTrue(behavior.buildValue().block != nil)
        behavior = behavior.onTranslation { _ in }
        XCTAssertTrue(behavior.buildValue().block != nil)
    }

    func testTranslationMapping() {
        let expectation = XCTestExpectation()
        let overlayTranslations = OverlayTranslation.translations()
        let expectedTranslations = Behavior.Translation.translations()
        XCTAssertEqual(overlayTranslations.count, expectedTranslations.count)
        expectation.expectedFulfillmentCount = overlayTranslations.count
        class Context {
            var translation: Behavior.Translation!
        }
        let context = Context()
        let action = { (translation: Behavior.Translation) in
            XCTAssertEqual(context.translation.containerSize, translation.containerSize)
            XCTAssertEqual(context.translation.height, translation.height)
            XCTAssertEqual(context.translation.progress, translation.progress)
            TestNotch.allCases.forEach {
                XCTAssertEqual(context.translation.height(for: $0), translation.height(for: $0))
            }
            expectation.fulfill()
        }
        zip(overlayTranslations, expectedTranslations).forEach { value, translation in
            context.translation = translation
            Behavior.empty().onTranslation(action).buildValue().block?(value)
        }
    }
}

private extension MagneticNotchOverlayBehavior.Translation where Notch == TestNotch {

    static func translations() -> [Self] {
        [
            Behavior.Translation(
                height: 30.0,
                transaction: Transaction(),
                progress: 0.0,
                containerSize: CGSize(width: 30.0, height: 30.0),
                heightForNotch: {
                    switch $0 {
                    case .max:
                        return 400
                    case .min:
                        return 200
                    }
                }
            ),
            Behavior.Translation(
                height: 30.0,
                transaction: Transaction(),
                progress: 0.0,
                containerSize: CGSize(width: 30.0, height: 30.0),
                heightForNotch: {
                    switch $0 {
                    case .max:
                        return 400
                    case .min:
                        return 200
                    }
                }
            ),
            Behavior.Translation(
                height: 30.0,
                transaction: Transaction(),
                progress: 0.5,
                containerSize: CGSize(width: 30.0, height: 30.0),
                heightForNotch: {
                    switch $0 {
                    case .max:
                        return 400
                    case .min:
                        return 200
                    }
                }
            ),
            Behavior.Translation(
                height: 10.0,
                transaction: Transaction(),
                progress: 1.0,
                containerSize: CGSize(width: 60.0, height: 90.0),
                heightForNotch: {
                    switch $0 {
                    case .max:
                        return 400
                    case .min:
                        return 200
                    }
                }
            )
        ]
    }
}

private extension OverlayTranslation {

    static func translations() -> [OverlayTranslation] {
        [
            OverlayTranslation(
                height: 30.0,
                transaction: Transaction(),
                isDragging: false,
                translationProgress: 0.0,
                containerFrame: CGRect(origin: .zero, size: CGSize(width: 30.0, height: 30.0)),
                velocity: .zero,
                heightForNotchIndex: { i -> CGFloat in
                    switch i {
                    case 0:
                        return 200.0
                    case 1:
                        return 400.0
                    default:
                        fatalError()
                    }
                }
            ),
            OverlayTranslation(
                height: 30.0,
                transaction: Transaction(),
                isDragging: false,
                translationProgress: -1.0,
                containerFrame: CGRect(origin: .zero, size: CGSize(width: 30.0, height: 30.0)),
                velocity: .zero,
                heightForNotchIndex: { i -> CGFloat in
                    switch i {
                    case 0:
                        return 200.0
                    case 1:
                        return 400.0
                    default:
                        fatalError()
                    }
                }
            ),
            OverlayTranslation(
                height: 30.0,
                transaction: Transaction(),
                isDragging: false,
                translationProgress: 0.5,
                containerFrame: CGRect(origin: .zero, size: CGSize(width: 30.0, height: 30.0)),
                velocity: .zero,
                heightForNotchIndex: { i -> CGFloat in
                    switch i {
                    case 0:
                        return 200.0
                    case 1:
                        return 400.0
                    default:
                        fatalError()
                    }
                }
            ),
            OverlayTranslation(
                height: 10.0,
                transaction: Transaction(),
                isDragging: false,
                translationProgress: 1.5,
                containerFrame: CGRect(origin: .zero, size: CGSize(width: 60.0, height: 90.0)),
                velocity: .zero,
                heightForNotchIndex: { i -> CGFloat in
                    switch i {
                    case 0:
                        return 200.0
                    case 1:
                        return 400.0
                    default:
                        fatalError()
                    }
                }
            )
        ]
    }
}

private extension Behavior {

    static func empty() -> Self {
        Behavior { Constant.dimension(for: $0) }
    }
}


================================================
FILE: Tests/DynamicOverlayTests/NotchBindingDynamicOverlayTests.swift
================================================
//
//  NotchBindingDynamicOverlayTests.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 10/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI
import XCTest
import DynamicOverlay

private enum Notch: CaseIterable, Equatable {
    case min
    case max
}

private struct Constants {

    static func height(for notch: Notch) -> CGFloat {
        switch notch {
        case .max:
            return 300.0
        case .min:
            return 200.0
        }
    }
}

private struct NotchChangeView: View {

    @ObservedObject
    var target: ValuePublisher<Notch>
    let onFrameChange: (CGRect) -> Void

    var body: some View {
        Color.red
            .dynamicOverlay(Color.green.onFrameChange(onFrameChange))
            .dynamicOverlayBehavior(behavior)
    }

    var behavior: some DynamicOverlayBehavior {
        MagneticNotchOverlayBehavior<Notch> { notch in
            .absolute(Double(Constants.height(for: notch)))
        }
        .notchChange($target.value)
    }
}

class NotchBindingDynamicOverlayTests: XCTestCase {

    func testInitialMaxNotch() {
        expectNotchHeight(.max)
    }

    func testInitialMinNotch() {
        expectNotchHeight(.min)
    }

    func testNotchChange() {
        class Context {
            var expectedHeight: CGFloat = 0.0
            var current = Notch.min
            var displayedFrame = CGRect.zero
            var expectations: [Notch: XCTestExpectation] = [:]
        }
        let target = ValuePublisher(Notch.min)
        let notches: [Notch] = [.min, .max]
        let context = Context()
        context.expectations = Dictionary(uniqueKeysWithValues: notches.map { ($0, XCTestExpectation()) })
        let view = NotchChangeView(target: target) { rect in
            context.displayedFrame = rect
            context.expectations[context.current]?.fulfill()
        }
        let renderer = ViewRenderer(view: view)
        notches.forEach { notch in
            guard let expectation = context.expectations[notch] else { return }
            context.current = notch
            target.update(notch)
            renderer.render()
            wait(for: [expectation], timeout: 1.0)
            let overlayFrame = renderer.window.bounds.intersection(context.displayedFrame)
            XCTAssertEqual(overlayFrame.height, Constants.height(for: notch))
            context.displayedFrame = .zero
        }
    }

    private func expectNotchHeight(_ notch: Notch) {
        let target = ValuePublisher(notch)
        var displayedFrame: CGRect = .zero
        let view = NotchChangeView(target: target) { rect in
            displayedFrame = rect
        }
        let renderer = ViewRenderer(view: view)
        renderer.render()
        let overlayFrame = renderer.window.bounds.intersection(displayedFrame)
        XCTAssertEqual(overlayFrame.height, Constants.height(for: notch))
    }
}


================================================
FILE: Tests/DynamicOverlayTests/NotchDimensionDynamicOverlayTests.swift
================================================
//
//  NotchDimensionDynamicOverlayTests.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import XCTest
import SwiftUI
import DynamicOverlay

private enum Notch: CaseIterable, Equatable {
    case min
}

private struct NotchDimensionView: View {

    let dimension: () -> NotchDimension
    let onHeightChange: (CGFloat) -> Void

    var body: some View {
        Color.red
            .dynamicOverlay(Color.green.onHeightChange(onHeightChange))
            .dynamicOverlayBehavior(behavior)
    }

    var behavior: some DynamicOverlayBehavior {
        MagneticNotchOverlayBehavior<Notch> { _ in
            dimension()
        }
    }
}

class NotchDimensionDynamicOverlayTests: XCTestCase {

    func testVariousDimensions() {
        class Context {
            var dimension: NotchDimension = .absolute(0)
            var expectedHeight: CGFloat = 0.0
            var expectation = XCTestExpectation()
        }
        let renderer = ViewRenderer(view: EmptyView())
        let resultByDimension: [NotchDimension: CGFloat] = [
            .absolute(-1) : 0.0,
            .absolute(0) : 0.0,
            .absolute(200.0) : 200.0,
            .fractional(-1.0) : 0.0,
            .fractional(0.0) : 0.0,
            .fractional(2.0) : renderer.safeBounds.height * 2.0,
            .fractional(0.5) : renderer.safeBounds.height * 0.5,
        ]
        resultByDimension.forEach { dimension, expectedHeight in
            let context = Context()
            let view = NotchDimensionView(dimension: { context.dimension }) { height in
                context.expectation.fulfill()
                XCTAssertEqual(context.expectedHeight.rounded(.up), height.rounded(.up))
            }
            context.expectedHeight = expectedHeight
            context.expectation = XCTestExpectation()
            context.dimension = dimension
            ViewRenderer(view: view).render()
            wait(for: [context.expectation], timeout: 0.3)
        }
    }
}


================================================
FILE: Tests/DynamicOverlayTests/NotchTranslationDynamicOverlayTests.swift
================================================
//
//  NotchTranslationDynamicOverlayTests.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 15/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI
import XCTest
@testable import DynamicOverlay

private enum Notch: CaseIterable, Equatable {
    case min, max
}

private struct Constants {

    static func insets(for layout: TranslationLayout) -> UIEdgeInsets {
        switch layout {
        case .compact:
            return UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
        case .full:
            return .zero
        }
    }

    static func height(for notch: Notch) -> CGFloat {
        switch notch {
        case .max:
            return 300.0
        case .min:
            return 100.0
        }
    }
}

private typealias OverlayTranslation = MagneticNotchOverlayBehavior<Notch>.Translation

private struct TranslationView: View {

    @ObservedObject
    var target: ValuePublisher<Notch>
    let onTranslation: (OverlayTranslation) -> Void

    var body: some View {
        Color.red
            .dynamicOverlay(Color.green)
            .dynamicOverlayBehavior(behavior)
    }

    private var behavior: MagneticNotchOverlayBehavior<Notch> {
        MagneticNotchOverlayBehavior { notch in
            .absolute(Double(Constants.height(for: notch)))
        }
        .notchChange($target.value)
        .onTranslation(onTranslation)
        .disable(.min)
    }
}

private enum TranslationLayout {
    case full, compact
}

private struct TranslationContainerView: View {

    let layout: TranslationLayout
    @ObservedObject
    var target: ValuePublisher<Notch>
    let onTranslation: (OverlayTranslation) -> Void

    var body: some View {
        TranslationView(
            target: target,
            onTranslation: onTranslation
        )
        .padding(insets)
    }

    private var insets: EdgeInsets {
        let layoutInsets = Constants.insets(for: layout)
        return EdgeInsets(
            top: layoutInsets.top,
            leading: layoutInsets.left,
            bottom: layoutInsets.bottom,
            trailing: layoutInsets.bottom
        )
    }
}

class NotchTranslationDynamicOverlayTests: XCTestCase {

    func testInitialTranslation() {
        class Context {
            var expectedTranslation = OverlayTranslation.none
        }
        let context = Context()
        let expectation = XCTestExpectation()
        expectation.expectedFulfillmentCount = Notch.allCases.count
        Notch.allCases.forEach { notch in
            let target = ValuePublisher(notch)
            let view = TranslationView(target: target) { translation in
                expectation.fulfill()
                self.expect(translation, toEqual: context.expectedTranslation)
            }
            let renderer = ViewRenderer(view: view)
            context.expectedTranslation = .initial(for: notch, in: renderer.safeBounds)
            renderer.render()
        }
        wait(for: [expectation], timeout: 1.0)
    }

    func testNotchMovesTranslation() {
        class Context {
            var expectedTranslation = OverlayTranslation.none
            var expectation = XCTestExpectation()
        }
        let context = Context()
        let target = ValuePublisher(Notch.min)
        let view = TranslationView(target: target) { translation in
            context.expectation.fulfill()
            self.expect(translation, toEqual: context.expectedTranslation)
        }
        // Initial
        let renderer = ViewRenderer(view: view)
        context.expectedTranslation = .initial(for: .min, in: renderer.safeBounds)
        renderer.render()
        wait(for: [context.expectation], timeout: 0.3)
        // Max not animated
        context.expectation = XCTestExpectation()
        context.expectedTranslation = .moved(to: .max, animated: false, in: renderer.safeBounds)
        target.update(.max)
        wait(for: [context.expectation], timeout: 0.3)
        // Min animated
        context.expectation = XCTestExpectation()
        context.expectedTranslation = .moved(to: .min, animated: true, in: renderer.safeBounds)
        withAnimation {
            target.update(.min)
        }
        wait(for: [context.expectation], timeout: 0.3)
        // Max animated
        context.expectation = XCTestExpectation()
        context.expectedTranslation = .moved(to: .max, animated: true, in: renderer.safeBounds)
        withAnimation {
            target.update(.max)
        }
        wait(for: [context.expectation], timeout: 0.3)
    }

    func testLayoutChangesTranslation() {
        class Context {
            var expected = OverlayTranslation.none
        }
        let layouts: [TranslationLayout] = [.compact, .full]
        layouts.forEach { layout in
            let context = Context()
            let expectation = XCTestExpectation()
            let target = ValuePublisher(Notch.min)
            let view = TranslationContainerView(
                layout: layout,
                target: target,
                onTranslation: { translation in
                    expectation.fulfill()
                    self.expect(translation, toEqual: context.expected)
                }
            )
            let renderer = ViewRenderer(view: view)
            context.expected = .layout(layout, in: renderer.safeBounds)
            renderer.render()
            wait(for: [expectation], timeout: 0.3)
        }
    }

    // MARK: - Private

    private func expect(_ lhs: OverlayTranslation, toEqual rhs: OverlayTranslation) {
        XCTAssertEqual(lhs.containerSize, rhs.containerSize)
        XCTAssertEqual(lhs.progress, rhs.progress)
        XCTAssertEqual(lhs.height, rhs.height)
        let lhsHasAnimation = lhs.transaction.animation != nil
        let rhsHasAnimation = rhs.transaction.animation != nil
        XCTAssertEqual(lhsHasAnimation, rhsHasAnimation)
        Notch.allCases.forEach {
            XCTAssertEqual(lhs.height(for: $0), rhs.height(for: $0))
        }
    }
}

private extension OverlayTranslation {

    static var none: OverlayTranslation {
        OverlayTranslation(height: 0, transaction: Transaction(), progress: 0, containerSize: .zero, heightForNotch: { _ in 0 })
    }

    static func initial(for notch: Notch, in bounds: CGRect) -> OverlayTranslation {
        .notch(notch, animated: false, in: bounds)
    }

    static func moved(to notch: Notch, animated: Bool, in bounds: CGRect) -> OverlayTranslation {
        .notch(notch, animated: animated, in: bounds)
    }

    static func layout(_ layout: TranslationLayout, in bounds: CGRect) -> OverlayTranslation {
        OverlayTranslation(
            height: Constants.height(for: .min),
            transaction: Transaction(),
            progress: 0.0,
            containerSize: bounds.inset(by: Constants.insets(for: layout)).size,
            heightForNotch: Constants.height(for:)
        )
    }

    private static func notch(_ notch: Notch, animated: Bool, in bounds: CGRect) -> OverlayTranslation {
        OverlayTranslation(
            height: Constants.height(for: notch),
            transaction: Transaction(animation: animated ? .default : nil),
            progress: notch == .max ? 1.0 : 0.0,
            containerSize: bounds.size,
            heightForNotch: Constants.height(for:)
        )
    }
}


================================================
FILE: Tests/DynamicOverlayTests/OverlayContainerRepresentableAdaptorTests.swift
================================================
//
//  OverlayContainerRepresentableAdaptorTests.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 20/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import XCTest
import SwiftUI
import OverlayContainer
@testable import DynamicOverlay

private struct AdaptorParameters {
    var drivingHandle: DynamicOverlayScrollViewProxy
    var handleValue: DynamicOverlayDragArea
    var disabledNotches: Set<Int> = []
    var indexToDimension: [Int: NotchDimension] = [:]
    var onIndexChange: ((Int) -> Void)?
}

class OverlayContainerRepresentableAdaptorTests: XCTestCase {

    class Context {
        let container: OverlayContainerViewController
        let coordinator: OverlayContainerCoordinator

        var overlay: UIViewController {
            container.topViewController!
        }

        init(container: OverlayContainerViewController,
             coordinator: OverlayContainerCoordinator) {
            self.container = container
            self.coordinator = coordinator
        }

        func layout() {
            container.view.frame = CGRect(origin: .zero, size: CGSize(width: 400, height: 400))
            container.view.layoutIfNeeded()
        }
    }

    func testViewControllersSetup() {
        let context = makeContext(
            for: AdaptorParameters(
                drivingHandle: .default,
                handleValue: .default,
                indexToDimension: [0: .absolute(200.0)]
            )
        )
        context.layout()
        XCTAssertEqual(context.container.viewControllers.count, 2)
    }

    func testDefaultDraggingStart() {
        let context = makeContext(
            for: AdaptorParameters(
                drivingHandle: .default,
                handleValue: .default,
                indexToDimension: [0: .absolute(200.0)]
            )
        )
        context.layout()
        let aboveOverlayPoint = CGPoint(x: 100, y: -10)
        XCTAssertEqual(
            context.container.delegate?.overlayContainerViewController(
                context.container,
                shouldStartDraggingOverlay: context.overlay,
                at: aboveOverlayPoint,
                in: context.overlay.view
            ),
            false
        )
        let inOverlayPoint = CGPoint(x: 100, y: 100)
        XCTAssertEqual(
            context.container.delegate?.overlayContainerViewController(
                context.container,
                shouldStartDraggingOverlay: context.overlay,
                at: inOverlayPoint,
                in: context.overlay.view
            ),
            true
        )
    }

    func testDisabledDraggingStart() {
        let context = makeContext(
            for: AdaptorParameters(
                drivingHandle: .default,
                handleValue: DynamicOverlayDragArea(area: .inactive()),
                indexToDimension: [0: .absolute(200.0)]
            )
        )
        context.layout()
        let aboveOverlayPoint = CGPoint(x: 100, y: -10)
        XCTAssertEqual(
            context.container.delegate?.overlayContainerViewController(
                context.container,
                shouldStartDraggingOverlay: context.overlay,
                at: aboveOverlayPoint,
                in: context.overlay.view
            ),
            false
        )
        let inOverlayPoint = CGPoint(x: 100, y: 100)
        XCTAssertEqual(
            context.container.delegate?.overlayContainerViewController(
                context.container,
                shouldStartDraggingOverlay: context.overlay,
                at: inOverlayPoint,
                in: context.overlay.view
            ),
            false
        )
    }

    func testEnabledDraggingStart() {
        let context = makeContext(
            for: AdaptorParameters(
                drivingHandle: .default,
                handleValue: DynamicOverlayDragArea(
                    area: .active(CGRect(origin: .zero, size: CGSize(width: 200, height: 300)))
                ),
                indexToDimension: [0: .absolute(200.0)]
            )
        )
        context.layout()
        let inZonePoint = CGPoint(x: 100, y: 100)
        XCTAssertEqual(
            context.container.delegate?.overlayContainerViewController(
                context.container,
                shouldStartDraggingOverlay: context.overlay,
                at: inZonePoint,
                in: context.overlay.view
            ),
            true
        )
        let outZonePoint = CGPoint(x: 300, y: 100)
        XCTAssertEqual(
            context.container.delegate?.overlayContainerViewController(
                context.container,
                shouldStartDraggingOverlay: context.overlay,
                at: outZonePoint,
                in: context.overlay.view
            ),
            false
        )
    }

    func testOverlayMoveNotification() {
        var index = 0
        let context = makeContext(
            for: AdaptorParameters(
                drivingHandle: .default,
                handleValue: DynamicOverlayDragArea(area: .default),
                indexToDimension: [0: .absolute(200.0), 1: .absolute(300.0)],
                onIndexChange: { index = $0 }
            )
        )
        context.layout()
        context.container.moveOverlay(toNotchAt: 1, animated: false)
        context.layout()
        XCTAssertEqual(index, 1)
    }

    func testNumberOfNotches() {
        let dimensions: [[Int: NotchDimension]] = [
            [0: .absolute(200)],
            [0: .absolute(200), 1: .absolute(300)],
            [0: .absolute(200), 1: .absolute(300), 3: .absolute(400)],
        ]
        dimensions.forEach { layout in
            let context = makeContext(
                for: AdaptorParameters(
                    drivingHandle: .default,
                    handleValue: .default,
                    indexToDimension: layout
                )
            )
            context.layout()
            XCTAssertEqual(
                context.container.delegate?.numberOfNotches(in: context.container) ?? 0,
                layout.count
            )
        }
    }

    func testDisabledNotches() {
        let all: [Int] = [0, 1, 2]
        let indexes: [Set<Int>] = [
            [],
            [0],
            [0, 1],
            [0, 1, 2],
            [2],
        ]
        indexes.forEach { disabledIndexes in
            let context = makeContext(
                for: AdaptorParameters(
                    drivingHandle: .default,
                    handleValue: .default,
                    disabledNotches: disabledIndexes,
                    indexToDimension: Dictionary(uniqueKeysWithValues: all.map { ($0, .absolute(100 * Double($0))) })
                )
            )
            context.layout()
            Set(all).subtracting(disabledIndexes).forEach { index in
                XCTAssertEqual(
                    context.container.delegate?.overlayContainerViewController(
                        context.container,
                        canReachNotchAt: index,
                        forOverlay: context.overlay
                    ) ?? false,
                    true
                )
            }
            disabledIndexes.forEach { index in
                XCTAssertEqual(
                    context.container.delegate?.overlayContainerViewController(
                        context.container,
                        canReachNotchAt: index,
                        forOverlay: context.overlay
                    ) ?? true,
                    false
                )
            }
        }
    }

    private func makeContext(for parameters: AdaptorParameters) -> Context {
        let holder = OverlayContainerPassiveContainer()
        holder.onNotchChange = parameters.onIndexChange
        let adaptor = OverlayContainerRepresentableAdaptor.init(
            containerState: OverlayContainerState(
                dragArea: parameters.handleValue,
                drivingScrollViewProxy: parameters.drivingHandle,
                notchIndex: nil,
                disabledNotches: parameters.disabledNotches,
                layout: OverlayContainerLayout(indexToDimension: parameters.indexToDimension)
            ),
            passiveContainer: holder,
            content: ContentView(),
            background: Color.green
        )
        let coordinator = adaptor.makeCoordinator()
        let context = OverlayContainerRepresentableAdaptor<ContentView, Color>.Context(
            coordinator: coordinator,
            transaction: Transaction()
        )
        let container = adaptor.makeUIViewController(context: context)
        adaptor.updateUIViewController(container, context: context)
        return Context(container: container, coordinator: coordinator)
    }
}

private struct ContentView: View {

    var body: some View {
        List { Text("") }
    }
}



================================================
FILE: Tests/DynamicOverlayTests/OverlayNotchIndexMapperTests.swift
================================================
//
//  OverlayNotchIndexMapperTests.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 29/12/2020.
//  Copyright © 2020 Fabernovel. All rights reserved.
//

import XCTest
@testable import DynamicOverlay

class OverlayNotchIndexMapperTests: XCTestCase {

    private var mapper: OverlayNotchIndexMapper!

    override func setUp() {
        self.mapper = OverlayNotchIndexMapper()
    }

    func testAlreadyOrderedIndexesMapping() {
        let layout = OverlayContainerLayout(
            indexToDimension: [
                0: NotchDimension(type: .fractional, value: 0.1),
                1: NotchDimension(type: .fractional, value: 0.2),
                2: NotchDimension(type: .fractional, value: 0.3),
            ]
        )
        mapper.reload(
            layout: layout,
            availableHeight: 200.0
        )
        XCTAssert(mapper.numberOfOverlayIndexes() == 3)
        XCTAssert(mapper.dynamicIndex(forOverlayIndex: 0) == 0)
        XCTAssert(mapper.dynamicIndex(forOverlayIndex: 1) == 1)
        XCTAssert(mapper.dynamicIndex(forOverlayIndex: 2) == 2)
        XCTAssert(mapper.overlayIndex(forDynamicIndex: 0) == 0)
        XCTAssert(mapper.overlayIndex(forDynamicIndex: 1) == 1)
        XCTAssert(mapper.overlayIndex(forDynamicIndex: 2) == 2)
    }

    func testFractionalNotchDimensionReordering() {
        let layout = OverlayContainerLayout(
            indexToDimension: [
                0: NotchDimension(type: .absolute, value: 100),
                1: NotchDimension(type: .fractional, value: 0.1),
            ]
        )
        mapper.reload(
            layout: layout,
            availableHeight: 200.0
        )
        XCTAssert(mapper.numberOfOverlayIndexes() == 2)
        XCTAssert(mapper.dynamicIndex(forOverlayIndex: 0) == 1)
        XCTAssert(mapper.dynamicIndex(forOverlayIndex: 1) == 0)
        XCTAssert(mapper.overlayIndex(forDynamicIndex: 1) == 0)
        XCTAssert(mapper.overlayIndex(forDynamicIndex: 0) == 1)
    }

    func testNotchFractionalHeights() {
        let layout = OverlayContainerLayout(
            indexToDimension: [
                0: NotchDimension(type: .fractional, value: 0.1),
                1: NotchDimension(type: .fractional, value: 0.5),
                2: NotchDimension(type: .fractional, value: 1),
            ]
        )
        mapper.reload(
            layout: layout,
            availableHeight: 200.0
        )
        XCTAssert(mapper.numberOfOverlayIndexes() == 3)
        XCTAssert(mapper.height(forOverlayIndex: 0) == 20.0)
        XCTAssert(mapper.height(forOverlayIndex: 1) == 100.0)
        XCTAssert(mapper.height(forOverlayIndex: 2) == 200.0)
    }
}


================================================
FILE: Tests/DynamicOverlayTests/Utils/ValuePublisher.swift
================================================
//
//  ValuePublisher.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 15/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation

class ValuePublisher<V>: ObservableObject {

    @Published
    var value: V

    init(_ value: V) {
        self.value = value
    }

    func update(_ value: V) {
        self.value = value
    }
}


================================================
FILE: Tests/DynamicOverlayTests/Utils/View+Measure.swift
================================================
//
//  View+OnHeightCHange.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 15/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI

extension View {

    func onHeightChange(_ block: @escaping (CGFloat) -> Void) -> some View {
        onFrameChange { block($0.height) }
    }

    func onFrameChange(in coordinateSpace: CoordinateSpace = .global,
                       _ block: @escaping (CGRect) -> Void) -> some View {
        modifier(OnFrameChangeViewModifier(coordinateSpace: coordinateSpace, block: block))
    }
}

private struct OnFrameChangeViewModifier: ViewModifier {

    let coordinateSpace: CoordinateSpace
    let block: (CGRect) -> Void

    func body(content: Content) -> some View {
        content.background(
            GeometryReader { proxy -> Color in
                let frame = proxy.frame(in: coordinateSpace)
                block(frame)
                return Color.clear
            }
        )
    }
}


================================================
FILE: Tests/DynamicOverlayTests/Utils/ViewInspector.swift
================================================
//
//  ViewInspector.swift
//  DynamicOverlayTests
//
//  Created by Gaétan Zanella on 16/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import UIKit

struct ViewInspector {

    let view: UIView

    func search<V: UIView>(_ type: V.Type) -> V {
        guard let result = view.search(type) else {
            fatalError("\(type) does not exist")
        }
        return result
    }
}

private extension UIView {

    func search<V: UIView>(_ type: V.Type) -> V? {
        if let result = self as? V {
            return result
        }
        for subview in subviews {
            if let target = subview.search(type) {
                return target
            }
        }
        return nil
    }
}


================================================
FILE: Tests/DynamicOverlayTests/Utils/ViewRenderer.swift
================================================
//
//  ViewRenderer.swift
//  DynamicOverlay
//
//  Created by Gaétan Zanella on 12/04/2021.
//  Copyright © 2021 Fabernovel. All rights reserved.
//

import Foundation
import SwiftUI

class ViewRenderer<V: View> {

    var size: CGSize {
        window.frame.size
    }

    var safeAreaInsets: UIEdgeInsets {
        window.safeAreaInsets
    }

    var bounds: CGRect {
        window.bounds
    }

    var safeBounds: CGRect {
        bounds.inset(by: safeAreaInsets)
    }

    var window: UIWindow {
        UIApplication.shared.windows.first!
    }

    private let hostController: UIHostingController<V>

    init(view: V) {
        self.hostController = UIHostingController(rootView: view)
    }

    func render() {
        if window.rootViewController !== hostController {
            window.rootViewController = hostController
        }
        CATransaction.flush()
    }
}


================================================
FILE: fastlane/Fastfile
================================================

default_platform(:ios)

platform :ios do
    desc "Run all unit tests"
    lane :tests do
        scan(
            scheme: "DynamicOverlay_Example",
            project: "DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj",
            clean: true
        )
    end

    desc "Pod linting"
    lane :pod_lint do
        pod_lib_lint(allow_warnings: true)
    end

    desc "Carthage linting"
    lane :carthage_lint do
        # FIX lint
        # carthage(command: "build", no_skip_current: true, platform: "iOS")
    end

    desc "SPM linting"
    lane :spm_lint do
        output = "Package.xcodeproj"
        Dir.chdir("..") do
            sh("swift package generate-xcodeproj --output #{output} --xcconfig-overrides #{ENV["XCCONFIG"]}")
        end
        xcodebuild(
            project: output,
            scheme: "#{ENV["SCHEME"]}-Package"
        )
        Dir.chdir("..") do
            sh("rm Package.resolved")
            sh("rm -rf Package.xcodeproj")
        end
    end

    desc "Release a new version"
    lane :release do |options|
        target_version = options[:version]
        raise "The version is missed. Use `fastlane release version:{version_number}`.`" if target_version.nil?

        ensure_git_branch(branch: "(release/*)|(hotfix/*)")
        ensure_git_status_clean

        podspec = ENV["PODSPEC"]
        version_bump_podspec(path: podspec, version_number: target_version)
        git_add
        git_commit(
            path: ["DynamicOverlay.podspec"],
            message: "Bump to #{target_version}"
        )
        ensure_git_status_clean
        add_git_tag(tag: target_version)

        changelog = read_changelog(
            changelog_path: "CHANGELOG.md",
            section_identifier: "[#{target_version}]"
        )

        # Push
        push_to_git_remote
        push_git_tags(tag: target_version)
        UI.success "Pushed 🎉"

        # Release cocoapods
        pod_push
        UI.success "Released 🎉"

        # Release Github

        set_github_release(
            repository_name: "faberNovel/DynamicOverlay",
            api_token: ENV["GITHUB_TOKEN"],
            name: "v#{target_version}",
            tag_name: "#{target_version}",
            description: changelog,
        )

        # Make PR
        create_pull_request(
            api_token: ENV["GITHUB_TOKEN"],
            repo: "faberNovel/DynamicOverlay",
            title: "Release #{target_version}",
            base: "main",
            body: changelog
        )
    end
end


================================================
FILE: fastlane/Pluginfile
================================================
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!

gem 'fastlane-plugin-changelog'
Download .txt
gitextract_s05uf70j/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── CHANGELOG.md
├── DynamicOverlay.podspec
├── DynamicOverlay_Example/
│   ├── DynamicOverlay_Example/
│   │   ├── Classes/
│   │   │   └── MapApp.swift
│   │   ├── Configuration/
│   │   │   └── Info.plist
│   │   ├── Resources/
│   │   │   ├── Assets.xcassets/
│   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   └── Base.lproj/
│   │   │       └── LaunchScreen.storyboard
│   │   ├── UIKit/
│   │   │   └── UIKitAppDelegate.swift
│   │   └── View/
│   │       ├── ActionCell.swift
│   │       ├── BackdropView.swift
│   │       ├── FavoriteCell.swift
│   │       ├── MapRootView.swift
│   │       ├── MapView.swift
│   │       ├── OverlayBackgroundView.swift
│   │       ├── OverlayView.swift
│   │       └── SearchBar.swift
│   └── DynamicOverlay_Example.xcodeproj/
│       ├── project.pbxproj
│       ├── project.xcworkspace/
│       │   ├── contents.xcworkspacedata
│       │   └── xcshareddata/
│       │       ├── IDEWorkspaceChecks.plist
│       │       └── swiftpm/
│       │           └── Package.resolved
│       └── xcshareddata/
│           └── xcschemes/
│               └── DynamicOverlay_Example.xcscheme
├── Gemfile
├── LICENSE
├── Package.resolved
├── Package.swift
├── Package.xcconfig
├── README.md
├── Source/
│   ├── DynamicOverlay.h
│   ├── Info.plist
│   ├── Internal/
│   │   ├── DynamicOverlayBehaviorValue.swift
│   │   ├── DynamicOverlayNotchTransition+DynamicOverlayBehavior.swift
│   │   ├── Handle/
│   │   │   ├── ActivatedOverlayArea.swift
│   │   │   ├── ActiveOverlayAreaViewModifier.swift
│   │   │   ├── Drag/
│   │   │   │   ├── DynamicOverlayDragArea.swift
│   │   │   │   └── OnDragAreaChangeViewModifier.swift
│   │   │   ├── DrivingScrollView/
│   │   │   │   ├── DynamicOverlayScrollViewProxy.swift
│   │   │   │   └── OnDrivingScrollViewChangeViewModifier.swift
│   │   │   └── OverlayContainerCoordinateSpace.swift
│   │   ├── MagneticNotchOverlayBehaviorValue.swift
│   │   ├── OverlayContainer/
│   │   │   ├── DynamicOverlayContainerAnimationController.swift
│   │   │   ├── OverlayContainerCoordinator.swift
│   │   │   ├── OverlayContainerDynamicOverlayView.swift
│   │   │   ├── OverlayContainerRepresentableAdaptor.swift
│   │   │   ├── OverlayContainerStateDiffer.swift
│   │   │   └── SwiftUIOverlayContainerRepresentableAdaptor.swift
│   │   ├── OverlayContentModifier.swift
│   │   ├── OverlayNotchIndexMapper.swift
│   │   └── Utils/
│   │       └── Binding+CaseIterable.swift
│   └── Public/
│       ├── DynamicOverlayBehavior.swift
│       ├── DynamicOverlayHandle.swift
│       ├── DynamicOverlayModifier.swift
│       └── MagneticNotchOverlayBehavior.swift
├── Tests/
│   └── DynamicOverlayTests/
│       ├── DragHandleViewModifierTests.swift
│       ├── DrivingScrollViewModifierTests.swift
│       ├── MagneticNotchOverlayBehaviorValueTests.swift
│       ├── NotchBindingDynamicOverlayTests.swift
│       ├── NotchDimensionDynamicOverlayTests.swift
│       ├── NotchTranslationDynamicOverlayTests.swift
│       ├── OverlayContainerRepresentableAdaptorTests.swift
│       ├── OverlayNotchIndexMapperTests.swift
│       └── Utils/
│           ├── ValuePublisher.swift
│           ├── View+Measure.swift
│           ├── ViewInspector.swift
│           └── ViewRenderer.swift
└── fastlane/
    ├── Fastfile
    └── Pluginfile
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (163K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 473,
    "preview": "name: CI\n\non: [push]\n\njobs:\n  build:\n    runs-on: macOS-latest\n\n    steps:\n      - uses: actions/checkout@v2\n      - nam"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 908,
    "preview": "\nname: Release\n\non:\n  workflow_dispatch:\n    inputs:\n          name:\n            description: 'Version name'\n           "
  },
  {
    "path": ".gitignore",
    "chars": 774,
    "preview": "# OS X\n.DS_Store\n\n# Xcode\n\n## Build generated\nDerivedData/\n\n## Various settings\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!de"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 244,
    "preview": "## [1.0.2]\n\n### Fixed\n\n- Jumping animation issue. #42\n\n## [1.0.0]\n\n### Fixed\n\n- Overlay with multiple scroll views #16\n\n"
  },
  {
    "path": "DynamicOverlay.podspec",
    "chars": 1209,
    "preview": "#\n# Be sure to run `pod lib lint DynamicOverlay.podspec' to ensure this is a\n# valid spec before submitting.\n#\n# Any lin"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/Classes/MapApp.swift",
    "chars": 426,
    "preview": "//\n//  MapApp.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 05/03/2019.\n//  Copyright © 2019 Fabe"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/Configuration/Info.plist",
    "chars": 1407,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1590,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/Resources/Assets.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/Resources/Base.lproj/LaunchScreen.storyboard",
    "chars": 1784,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/UIKit/UIKitAppDelegate.swift",
    "chars": 525,
    "preview": "//\n//  UIKitAppDelegate.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 18/04/2021.\n//  Copyright ©"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/ActionCell.swift",
    "chars": 301,
    "preview": "//\n//  ActionCell.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 19/04/2021.\n//  Copyright © 2021 "
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/BackdropView.swift",
    "chars": 289,
    "preview": "//\n//  Backdropview.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 19/04/2021.\n//  Copyright © 202"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/FavoriteCell.swift",
    "chars": 577,
    "preview": "//\n//  FavoriteCell.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 19/04/2021.\n//  Copyright © 202"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/MapRootView.swift",
    "chars": 1732,
    "preview": "//\n//  MapRootView.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 17/04/2021.\n//  Copyright © 2021"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/MapView.swift",
    "chars": 504,
    "preview": "//\n//  MapView.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 17/04/2021.\n//  Copyright © 2021 Fab"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/OverlayBackgroundView.swift",
    "chars": 993,
    "preview": "//\n//  OverlayBackgroundView.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 19/04/2021.\n//  Copyri"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/OverlayView.swift",
    "chars": 1453,
    "preview": "//\n//  OverlayView.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 17/04/2021.\n//  Copyright © 2021"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example/View/SearchBar.swift",
    "chars": 1689,
    "preview": "//\n//  SearchBar.swift\n//  DynamicOverlay_Example\n//\n//  Created by Gaétan Zanella on 18/04/2021.\n//  Copyright © 2021 F"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.pbxproj",
    "chars": 30330,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 52;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "chars": 341,
    "preview": "{\n  \"object\": {\n    \"pins\": [\n      {\n        \"package\": \"OverlayContainer\",\n        \"repositoryURL\": \"https://github.co"
  },
  {
    "path": "DynamicOverlay_Example/DynamicOverlay_Example.xcodeproj/xcshareddata/xcschemes/DynamicOverlay_Example.xcscheme",
    "chars": 3450,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1400\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Gemfile",
    "chars": 214,
    "preview": "source 'https://rubygems.org'\n\ngem \"cocoapods\", \"~> 1.11\"\ngem \"fastlane\", \"~> 2.1\"\nplugins_path = File.join(File.dirname"
  },
  {
    "path": "LICENSE",
    "chars": 1054,
    "preview": "Copyright (c) 2020 Fabernovel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this soft"
  },
  {
    "path": "Package.resolved",
    "chars": 341,
    "preview": "{\n  \"object\": {\n    \"pins\": [\n      {\n        \"package\": \"OverlayContainer\",\n        \"repositoryURL\": \"https://github.co"
  },
  {
    "path": "Package.swift",
    "chars": 737,
    "preview": "// swift-tools-version:5.1\nimport PackageDescription\n\nlet package = Package(\n    name: \"DynamicOverlay\",\n    platforms: "
  },
  {
    "path": "Package.xcconfig",
    "chars": 562,
    "preview": "IPHONEOS_DEPLOYMENT_TARGET = 10.0\n\nSDKROOT =\nSUPPORTED_PLATFORMS = iphoneos iphonesimulator\nTARGETED_DEVICE_FAMILY = 1,2"
  },
  {
    "path": "README.md",
    "chars": 9324,
    "preview": "# DynamicOverlay\n\n<H4 align=\"center\">\nDynamicOverlay is a SwiftUI library. It makes easier to develop overlay based inte"
  },
  {
    "path": "Source/DynamicOverlay.h",
    "chars": 535,
    "preview": "//\n//  DynamicOverlay.h\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 05/03/2019.\n//  Copyright © 2019 Fabernov"
  },
  {
    "path": "Source/Info.plist",
    "chars": 704,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Source/Internal/DynamicOverlayBehaviorValue.swift",
    "chars": 1673,
    "preview": "//\n//  EmptyFile.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 05/03/2019.\n//  Copyright © 2019 Fabernove"
  },
  {
    "path": "Source/Internal/DynamicOverlayNotchTransition+DynamicOverlayBehavior.swift",
    "chars": 1557,
    "preview": "//\n//  MagneticNotchOverlayBehavior+DynamicOverlayBehavior.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on "
  },
  {
    "path": "Source/Internal/Handle/ActivatedOverlayArea.swift",
    "chars": 1443,
    "preview": "//\n//  ActivatedOverlayArea.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 04/12/2020.\n//  Copyright © 202"
  },
  {
    "path": "Source/Internal/Handle/ActiveOverlayAreaViewModifier.swift",
    "chars": 661,
    "preview": "//\n//  ActiveOverlayAreaViewModifier.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 28/12/2020.\n//  Copyri"
  },
  {
    "path": "Source/Internal/Handle/Drag/DynamicOverlayDragArea.swift",
    "chars": 958,
    "preview": "//\n//  DynamicOverlayDragArea.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 29/01/2022.\n//  Copyright © 2"
  },
  {
    "path": "Source/Internal/Handle/Drag/OnDragAreaChangeViewModifier.swift",
    "chars": 712,
    "preview": "//\n//  OnDragAreaChangeViewModifier.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/04/2021.\n//  Co"
  },
  {
    "path": "Source/Internal/Handle/DrivingScrollView/DynamicOverlayScrollViewProxy.swift",
    "chars": 1455,
    "preview": "//\n//  DynamicOverlayScrollViewProxyPreferenceKey.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 11/01/202"
  },
  {
    "path": "Source/Internal/Handle/DrivingScrollView/OnDrivingScrollViewChangeViewModifier.swift",
    "chars": 770,
    "preview": "//\n//  OnDrivingScrollViewChangeViewModifier.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/04/202"
  },
  {
    "path": "Source/Internal/Handle/OverlayContainerCoordinateSpace.swift",
    "chars": 595,
    "preview": "//\n//  OverlayContainer+CoordinateSpace.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 28/12/2020.\n//  Cop"
  },
  {
    "path": "Source/Internal/MagneticNotchOverlayBehaviorValue.swift",
    "chars": 1997,
    "preview": "//\n//  MagneticNotchOverlayBehaviorValue.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 02/12/2020.\n//  Co"
  },
  {
    "path": "Source/Internal/OverlayContainer/DynamicOverlayContainerAnimationController.swift",
    "chars": 3527,
    "preview": "//\n//  DynamicOverlayContainerAnimationController.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 28/12/202"
  },
  {
    "path": "Source/Internal/OverlayContainer/OverlayContainerCoordinator.swift",
    "chars": 7995,
    "preview": "//\n//  OverlayContainerCoordinator.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 02/12/2020.\n//  Copyrigh"
  },
  {
    "path": "Source/Internal/OverlayContainer/OverlayContainerDynamicOverlayView.swift",
    "chars": 2285,
    "preview": "//\n//  OverlayContainerView.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 02/12/2020.\n//  Copyright © 202"
  },
  {
    "path": "Source/Internal/OverlayContainer/OverlayContainerRepresentableAdaptor.swift",
    "chars": 2036,
    "preview": "//\n//  OverlayContainerRepresentableAdaptor.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 20/04/2021"
  },
  {
    "path": "Source/Internal/OverlayContainer/OverlayContainerStateDiffer.swift",
    "chars": 986,
    "preview": "//\n//  OverlayContainerStateDiffer.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 23/07/2021.\n//  Copyrigh"
  },
  {
    "path": "Source/Internal/OverlayContainer/SwiftUIOverlayContainerRepresentableAdaptor.swift",
    "chars": 1224,
    "preview": "//\n//  SwiftUIOverlayContainerRepresentableAdaptor.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/"
  },
  {
    "path": "Source/Internal/OverlayContentModifier.swift",
    "chars": 1230,
    "preview": "//\n//  OverlayContentModifier.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 03/12/2020.\n//  Copyright © 2"
  },
  {
    "path": "Source/Internal/OverlayNotchIndexMapper.swift",
    "chars": 1884,
    "preview": "//\n//  OverlayNotchIndexMapper.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 29/12/2020.\n//  Copyright © "
  },
  {
    "path": "Source/Internal/Utils/Binding+CaseIterable.swift",
    "chars": 977,
    "preview": "//\n//  Binding+CaseIterable.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 02/12/2020.\n//  Copyright © 202"
  },
  {
    "path": "Source/Public/DynamicOverlayBehavior.swift",
    "chars": 334,
    "preview": "//\n//  DynamicOverlayBehavior.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 02/12/2020.\n//  Copyright © 2"
  },
  {
    "path": "Source/Public/DynamicOverlayHandle.swift",
    "chars": 1115,
    "preview": "//\n//  DynamicOverlayHandle.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 04/12/2020.\n//  Copyright © 202"
  },
  {
    "path": "Source/Public/DynamicOverlayModifier.swift",
    "chars": 1613,
    "preview": "//\n//  DynamicOverlayModifier.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 01/12/2020.\n//  Copyright © 2"
  },
  {
    "path": "Source/Public/MagneticNotchOverlayBehavior.swift",
    "chars": 3830,
    "preview": "//\n//  MagneticNotchOverlayBehavior.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 02/12/2020.\n//  Copyrig"
  },
  {
    "path": "Tests/DynamicOverlayTests/DragHandleViewModifierTests.swift",
    "chars": 2331,
    "preview": "//\n//  DragHandleTests.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/04/2021.\n//  Copyright © 202"
  },
  {
    "path": "Tests/DynamicOverlayTests/DrivingScrollViewModifierTests.swift",
    "chars": 3194,
    "preview": "//\n//  DrivingScrollViewModifierTests.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/04/2021.\n//  "
  },
  {
    "path": "Tests/DynamicOverlayTests/MagneticNotchOverlayBehaviorValueTests.swift",
    "chars": 8191,
    "preview": "//\n//  MagneticNotchOverlayBehaviorValueTests.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/04/20"
  },
  {
    "path": "Tests/DynamicOverlayTests/NotchBindingDynamicOverlayTests.swift",
    "chars": 2926,
    "preview": "//\n//  NotchBindingDynamicOverlayTests.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 10/04/2021.\n//  Copy"
  },
  {
    "path": "Tests/DynamicOverlayTests/NotchDimensionDynamicOverlayTests.swift",
    "chars": 2064,
    "preview": "//\n//  NotchDimensionDynamicOverlayTests.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/04/2021.\n/"
  },
  {
    "path": "Tests/DynamicOverlayTests/NotchTranslationDynamicOverlayTests.swift",
    "chars": 7352,
    "preview": "//\n//  NotchTranslationDynamicOverlayTests.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 15/04/2021.\n//  "
  },
  {
    "path": "Tests/DynamicOverlayTests/OverlayContainerRepresentableAdaptorTests.swift",
    "chars": 8902,
    "preview": "//\n//  OverlayContainerRepresentableAdaptorTests.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 20/04"
  },
  {
    "path": "Tests/DynamicOverlayTests/OverlayNotchIndexMapperTests.swift",
    "chars": 2665,
    "preview": "//\n//  OverlayNotchIndexMapperTests.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 29/12/2020.\n//  Copyrig"
  },
  {
    "path": "Tests/DynamicOverlayTests/Utils/ValuePublisher.swift",
    "chars": 372,
    "preview": "//\n//  ValuePublisher.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 15/04/2021.\n//  Copyright © 2021 Fabe"
  },
  {
    "path": "Tests/DynamicOverlayTests/Utils/View+Measure.swift",
    "chars": 994,
    "preview": "//\n//  View+OnHeightCHange.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 15/04/2021.\n//  Copyright © 2021"
  },
  {
    "path": "Tests/DynamicOverlayTests/Utils/ViewInspector.swift",
    "chars": 727,
    "preview": "//\n//  ViewInspector.swift\n//  DynamicOverlayTests\n//\n//  Created by Gaétan Zanella on 16/04/2021.\n//  Copyright © 2021 "
  },
  {
    "path": "Tests/DynamicOverlayTests/Utils/ViewRenderer.swift",
    "chars": 887,
    "preview": "//\n//  ViewRenderer.swift\n//  DynamicOverlay\n//\n//  Created by Gaétan Zanella on 12/04/2021.\n//  Copyright © 2021 Fabern"
  },
  {
    "path": "fastlane/Fastfile",
    "chars": 2521,
    "preview": "\ndefault_platform(:ios)\n\nplatform :ios do\n    desc \"Run all unit tests\"\n    lane :tests do\n        scan(\n            sch"
  },
  {
    "path": "fastlane/Pluginfile",
    "chars": 115,
    "preview": "# Autogenerated by fastlane\n#\n# Ensure this file is checked in to source control!\n\ngem 'fastlane-plugin-changelog'\n"
  }
]

About this extraction

This page contains the full source code of the faberNovel/DynamicOverlay GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (145.5 KB), approximately 38.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!