Browse Source

Add scroll view support

master
Shadowfacts 2 years ago
parent
commit
c34938a03d
Signed by: shadowfacts GPG Key ID: 94A5AB95422746E5
  1. 12
      SheetImagePicker.xcodeproj/project.pbxproj
  2. 111
      SheetImagePicker/SheetContainerViewController.swift
  3. 41
      SheetImagePickerTest/Base.lproj/Main.storyboard
  4. 43
      SheetImagePickerTest/ContentTableViewController.swift
  5. 20
      SheetImagePickerTest/ViewController.swift

12
SheetImagePicker.xcodeproj/project.pbxproj

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
D6055E46234D1B31007BEF52 /* ContentTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6055E45234D1B31007BEF52 /* ContentTableViewController.swift */; };
D610D2D6233945AF009EB06A /* SheetImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = D610D2D4233945AF009EB06A /* SheetImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; };
D610D2E3233945B9009EB06A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D610D2E2233945B9009EB06A /* AppDelegate.swift */; };
D610D2E5233945B9009EB06A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D610D2E4233945B9009EB06A /* SceneDelegate.swift */; };
@ -48,6 +49,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
D6055E45234D1B31007BEF52 /* ContentTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTableViewController.swift; sourceTree = "<group>"; };
D610D2D1233945AF009EB06A /* SheetImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SheetImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D610D2D4233945AF009EB06A /* SheetImagePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SheetImagePicker.h; sourceTree = "<group>"; };
D610D2D5233945AF009EB06A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -124,6 +126,7 @@
D610D2E2233945B9009EB06A /* AppDelegate.swift */,
D610D2E4233945B9009EB06A /* SceneDelegate.swift */,
D610D2E6233945B9009EB06A /* ViewController.swift */,
D6055E45234D1B31007BEF52 /* ContentTableViewController.swift */,
D610D2E8233945B9009EB06A /* Main.storyboard */,
D610D2EB233945BB009EB06A /* Assets.xcassets */,
D610D2ED233945BB009EB06A /* LaunchScreen.storyboard */,
@ -267,6 +270,7 @@
files = (
D610D2E7233945B9009EB06A /* ViewController.swift in Sources */,
D610D2E3233945B9009EB06A /* AppDelegate.swift in Sources */,
D6055E46234D1B31007BEF52 /* ContentTableViewController.swift in Sources */,
D610D2E5233945B9009EB06A /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -478,13 +482,13 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW;
DEVELOPMENT_TEAM = 46G5674823;
INFOPLIST_FILE = SheetImagePickerTest/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.SheetImagePickerTest;
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.dev.SheetImagePickerTest;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -497,13 +501,13 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW;
DEVELOPMENT_TEAM = 46G5674823;
INFOPLIST_FILE = SheetImagePickerTest/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.SheetImagePickerTest;
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.dev.SheetImagePickerTest;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";

111
SheetImagePicker/SheetContainerViewController.swift

@ -44,6 +44,7 @@ public class SheetContainerViewController: UIViewController {
var dimmingView: UIView!
public var minimumDimmingAlpha: CGFloat = 0
public var maximumDimmingAlpha: CGFloat = 0.75
var initialScrollViewContentOffset = CGPoint.zero
public init(content: UIViewController) {
self.content = content
@ -84,8 +85,12 @@ public class SheetContainerViewController: UIViewController {
dimmingView.bottomAnchor.constraint(equalTo: content.view.topAnchor, constant: content.view.layer.cornerRadius)
])
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:)))
content.view.addGestureRecognizer(panGesture)
if let scrollView = content.view as? UIScrollView {
scrollView.panGestureRecognizer.addTarget(self, action: #selector(scrollViewPanGestureRecognized))
} else {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:)))
content.view.addGestureRecognizer(panGesture)
}
}
@objc func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
@ -95,44 +100,90 @@ public class SheetContainerViewController: UIViewController {
case .changed:
let translation = recognizer.translation(in: content.view)
var realOffset = initialConstant + translation.y
let topOffset = topDetent.offset
if realOffset < topOffset {
let smoothed = smoothstep(value: realOffset, from: topOffset, to: 0)
realOffset = topOffset - smoothed * maximumStretchDistance
}
topConstraint.constant = realOffset
dimmingView.alpha = lerp(realOffset, min: topOffset, max: bottomDetent.offset, from: maximumDimmingAlpha, to: minimumDimmingAlpha)
let realOffset = initialConstant + translation.y
setTopOffset(realOffset)
case .ended:
let velocity = recognizer.velocity(in: view)
springToNearestDetent(verticalVelocity: velocity.y)
let springToDetent: (Detent, CGFloat)
if abs(velocity.y) > minimumDetentJumpVelocity,
let detentInVelocityDirection = nearestDetentOffset(currentOffset: topConstraint.constant, direction: velocity.y) {
springToDetent = detentInVelocityDirection
} else if let nearestDetent = nearestDetentOffset(currentOffset: topConstraint.constant) {
springToDetent = nearestDetent
} else {
return
default:
break
}
}
@objc func scrollViewPanGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
guard let scrollView = recognizer.view as? UIScrollView else { return }
let translation = recognizer.translation(in: scrollView)
let velocity = recognizer.velocity(in: scrollView)
let shouldMoveSheetDown = scrollView.contentOffset.y <= 0 && velocity.y > 0 // scrolled to top and dragging down
let shouldMoveSheetUp = topConstraint.constant > topDetent.offset && velocity.y < 0 // not fully expanded and dragging up
let shouldMoveSheet = shouldMoveSheetDown || shouldMoveSheetUp
if shouldMoveSheet {
scrollView.bounces = false
scrollView.setContentOffset(.zero, animated: false)
}
switch recognizer.state {
case .began:
initialScrollViewContentOffset = scrollView.contentOffset
case .changed:
if shouldMoveSheet {
setTopOffset(topConstraint.constant + translation.y)
recognizer.setTranslation(initialScrollViewContentOffset, in: scrollView)
}
if delegate?.sheetContainer(self, willSnapToDetent: springToDetent.0) ?? true {
let springDistance = abs(topConstraint.constant - springToDetent.1)
self.topConstraint.constant = springToDetent.1
let springVelocity = velocity.y / springDistance
UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 0.75, initialSpringVelocity: springVelocity, animations: {
self.view.layoutIfNeeded()
self.dimmingView.alpha = lerp(springToDetent.1, min: self.topDetent.offset, max: self.bottomDetent.offset, from: self.maximumDimmingAlpha, to: self.minimumDimmingAlpha)
}, completion: { (finished) in
self.delegate?.sheetContainer(self, didSnapToDetent: springToDetent.0)
})
case .ended:
scrollView.bounces = true
if shouldMoveSheet {
springToNearestDetent(verticalVelocity: velocity.y)
}
default:
break
}
}
func setTopOffset(_ offset: CGFloat) {
var offset = offset
let topOffset = topDetent.offset
if offset < topOffset {
let smoothed = smoothstep(value: offset, from: topOffset, to: 0)
offset = topOffset - smoothed * maximumStretchDistance
}
topConstraint.constant = offset
dimmingView.alpha = lerp(offset, min: topOffset, max: bottomDetent.offset, from: maximumDimmingAlpha, to: minimumDimmingAlpha)
}
func springToNearestDetent(verticalVelocity velocity: CGFloat) {
let springToDetent: (Detent, CGFloat)
if abs(velocity) > minimumDetentJumpVelocity,
let detentInVelocityDirection = nearestDetentOffset(currentOffset: topConstraint.constant, direction: velocity) {
springToDetent = detentInVelocityDirection
} else if let nearestDetent = nearestDetentOffset(currentOffset: topConstraint.constant) {
springToDetent = nearestDetent
} else {
return
}
if delegate?.sheetContainer(self, willSnapToDetent: springToDetent.0) ?? true {
let springDistance = abs(topConstraint.constant - springToDetent.1)
self.topConstraint.constant = springToDetent.1
let springVelocity = velocity / springDistance
UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 0.75, initialSpringVelocity: springVelocity, animations: {
self.view.layoutIfNeeded()
self.dimmingView.alpha = lerp(springToDetent.1, min: self.topDetent.offset, max: self.bottomDetent.offset, from: self.maximumDimmingAlpha, to: self.minimumDimmingAlpha)
}, completion: { (finished) in
self.delegate?.sheetContainer(self, didSnapToDetent: springToDetent.0)
})
}
}
func nearestDetentOffset(currentOffset: CGFloat) -> (Detent, CGFloat)? {

41
SheetImagePickerTest/Base.lproj/Main.storyboard

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -15,18 +15,37 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tfd-T0-fMO">
<rect key="frame" x="184" y="433" width="46" height="30"/>
<state key="normal" title="Button"/>
<connections>
<action selector="buttonPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="tmi-VL-j61"/>
</connections>
</button>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="hIF-hV-InX">
<rect key="frame" x="188.5" y="403" width="37" height="90"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tfd-T0-fMO">
<rect key="frame" x="0.0" y="0.0" width="37" height="30"/>
<state key="normal" title="Plain"/>
<connections>
<action selector="plainPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="tmi-VL-j61"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fT8-yG-J2R">
<rect key="frame" x="0.0" y="30" width="37" height="30"/>
<state key="normal" title="Table"/>
<connections>
<action selector="tablePressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="PZl-oC-0t4"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="39x-b2-4Hx">
<rect key="frame" x="0.0" y="60" width="37" height="30"/>
<state key="normal" title="Nav"/>
<connections>
<action selector="navPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Jc9-R9-VZW"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="tfd-T0-fMO" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Cpn-cM-hYr"/>
<constraint firstItem="tfd-T0-fMO" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="YJH-oX-yBz"/>
<constraint firstItem="hIF-hV-InX" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="MT3-wG-T9R"/>
<constraint firstItem="hIF-hV-InX" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="Unx-Jo-GQK"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>

43
SheetImagePickerTest/ContentTableViewController.swift

@ -0,0 +1,43 @@
//
// ContentTableViewController.swift
// SheetImagePickerTest
//
// Created by Shadowfacts on 10/8/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
class ContentTableViewController: UITableViewController {
init() {
super.init(style: .plain)
}
required init?(coder: NSCoder) {
fatalError()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "testCell")
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 40
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testCell", for: indexPath)
cell.textLabel!.text = "\(indexPath.row)"
return cell
}
}

20
SheetImagePickerTest/ViewController.swift

@ -17,12 +17,9 @@ class ViewController: UIViewController {
view.backgroundColor = .green
}
@IBAction func buttonPressed(_ sender: Any) {
@IBAction func plainPressed(_ sender: Any) {
let content = UIViewController()
content.view.translatesAutoresizingMaskIntoConstraints = false
content.view.layer.masksToBounds = true
content.view.layer.cornerRadius = view.bounds.width * 0.02
let blurEffect = UIBlurEffect(style: .systemChromeMaterial)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
@ -39,6 +36,19 @@ class ViewController: UIViewController {
label.centerXAnchor.constraint(equalTo: blurView.contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: blurView.contentView.centerYAnchor)
])
let sheet = SheetContainerViewController(content: content)
sheet.delegate = self
sheet.detents = [.bottom, .middle, .top]
present(sheet, animated: true)
}
@IBAction func tablePressed(_ sender: Any) {
let content = ContentTableViewController()
content.view.translatesAutoresizingMaskIntoConstraints = false
content.view.layer.masksToBounds = true
content.view.layer.cornerRadius = view.bounds.width * 0.02
let sheet = SheetContainerViewController(content: content)
sheet.delegate = self
@ -47,6 +57,8 @@ class ViewController: UIViewController {
present(sheet, animated: true)
}
@IBAction func navPressed(_ sender: Any) {
}
}
extension ViewController: SheetContainerViewControllerDelegate {

Loading…
Cancel
Save