ChatGPT解决这个技术问题 Extra ChatGPT

如何模仿地图应用程序的底部工作表?

谁能告诉我如何在 iOS 10 的新 Apple Maps 应用程序中模仿底部表格?

在 Android 中,您可以使用模仿这种行为的 BottomSheet,但我在 iOS 中找不到类似的东西。

这是一个带有内容插入的简单滚动视图,因此搜索栏位于底部?

我对 iOS 编程相当陌生,所以如果有人可以帮助我创建这个布局,那将不胜感激。

这就是我所说的“底页”:

https://i.stack.imgur.com/doVzSm.jpg

恐怕你必须自己建造它。这是一个具有模糊背景视图和一些手势识别器的视图。
@KhanhNguyen 是的,我知道我必须自己构建它,但我想知道哪些视图用于实现这种效果以及它们的排序方式等等
它是具有模糊效果的视觉效果视图。看到这个SO question
我认为您可以将平移手势识别器添加到底部视图并相应地移动视图(通过存储手势识别器事件之间的 y 坐标来观察变化,并计算差异)。
我喜欢你的问题。我正计划实施这样的事情。如果有人可以写一些例子会很好。

A
Ahmad Elassuty

我不知道新地图应用程序的底页究竟如何响应用户交互。但是您可以创建一个类似于屏幕截图中的自定义视图并将其添加到主视图中。

我假设你知道如何:

1-通过情节提要或使用 xib 文件创建视图控制器。

2- 使用 googleMaps 或 Apple 的 MapKit。

例子

1- 创建 2 个视图控制器,例如 MapViewController 和 BottomSheetViewController。第一个控制器将托管地图,第二个控制器是底部工作表本身。

配置 MapViewController

创建一个方法来添加底部工作表视图。

func addBottomSheetView() {
    // 1- Init bottomSheetVC
    let bottomSheetVC = BottomSheetViewController()

    // 2- Add bottomSheetVC as a child view 
    self.addChildViewController(bottomSheetVC)
    self.view.addSubview(bottomSheetVC.view)
    bottomSheetVC.didMoveToParentViewController(self)

    // 3- Adjust bottomSheet frame and initial position.
    let height = view.frame.height
    let width  = view.frame.width
    bottomSheetVC.view.frame = CGRectMake(0, self.view.frame.maxY, width, height)
}

并在 viewDidAppear 方法中调用它:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    addBottomSheetView()
}

配置 BottomSheetViewController

1) 准备背景

创建一种方法来添加模糊和活力效果

func prepareBackgroundView(){
    let blurEffect = UIBlurEffect.init(style: .Dark)
    let visualEffect = UIVisualEffectView.init(effect: blurEffect)
    let bluredView = UIVisualEffectView.init(effect: blurEffect)
    bluredView.contentView.addSubview(visualEffect)

    visualEffect.frame = UIScreen.mainScreen().bounds
    bluredView.frame = UIScreen.mainScreen().bounds

    view.insertSubview(bluredView, atIndex: 0)
}

在你的 viewWillAppear 中调用这个方法

override func viewWillAppear(animated: Bool) {
   super.viewWillAppear(animated)
   prepareBackgroundView()
}

确保控制器的视图背景颜色为 clearColor。

2)动画bottomSheet外观

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    UIView.animateWithDuration(0.3) { [weak self] in
        let frame = self?.view.frame
        let yComponent = UIScreen.mainScreen().bounds.height - 200
        self?.view.frame = CGRectMake(0, yComponent, frame!.width, frame!.height)
    }
}

3)根据需要修改您的xib。

4) 将平移手势识别器添加到您的视图中。

在您的 viewDidLoad 方法中添加 UIPanGestureRecognizer。

override func viewDidLoad() {
    super.viewDidLoad()

    let gesture = UIPanGestureRecognizer.init(target: self, action: #selector(BottomSheetViewController.panGesture))
    view.addGestureRecognizer(gesture)

}

并实现你的手势行为:

func panGesture(recognizer: UIPanGestureRecognizer) {
    let translation = recognizer.translationInView(self.view)
    let y = self.view.frame.minY
    self.view.frame = CGRectMake(0, y + translation.y, view.frame.width, view.frame.height)
     recognizer.setTranslation(CGPointZero, inView: self.view)
}

可滚动的底页:

如果您的自定义视图是滚动视图或任何其他继承自的视图,那么您有两种选择:

第一的:

设计带有标题视图的视图并将 panGesture 添加到标题中。 (糟糕的用户体验)。

第二:

1 - 将 panGesture 添加到底部工作表视图。

2 - 实现 UIGestureRecognizerDelegate 并将 panGesture 委托设置为控制器。

3- 实现 shouldRecognizeSimultaneouslyWith 委托函数并在两种情况下禁用 scrollView isScrollEnabled 属性:

该视图是部分可见的。

视图完全可见,scrollView contentOffset 属性为 0,用户正在向下拖动视图。

否则启用滚动。

  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
      let gesture = (gestureRecognizer as! UIPanGestureRecognizer)
      let direction = gesture.velocity(in: view).y

      let y = view.frame.minY
      if (y == fullView && tableView.contentOffset.y == 0 && direction > 0) || (y == partialView) {
          tableView.isScrollEnabled = false
      } else {
        tableView.isScrollEnabled = true
      }

      return false
  }

笔记

如果您将 .allowUserInteraction 设置为动画选项,例如在示例项目中,so you need to enable scrolling on the animation completion closure if the user is scrolling up.

示例项目

我在 this 存储库上创建了一个包含更多选项的示例项目,它可以让您更好地了解如何自定义流程。

In the demo, addBottomSheetView() function controls which view should be used as a bottom sheet.

示例项目截图

局部视图

https://i.stack.imgur.com/AQ7fW.png

全视图

https://i.stack.imgur.com/YVOPS.png

可滚动视图

https://i.stack.imgur.com/GoN5K.png


这实际上是 childViews 和 subViews 的一个很好的教程。谢谢 :)
@AhmedElassuty 如何在 xib 中添加 tableview 并加载为底页?如果您能提供帮助将非常感谢提前!
@nikhilnangia我刚刚用另一个包含tableView的viewController“ScrollableBottomSheetViewController.swift”更新了repo。我会尽快更新答案。也检查一下github.com/AhmedElassuty/IOS-BottomSheet/issues/1
我注意到Apple Maps 底部表单处理从表单拖动手势到滚动视图滚动手势的平滑动作,即用户不需要停止一个手势并开始一个新手势即可开始滚动视图。你的例子没有这样做。您对如何实现这一点有任何想法吗?谢谢,非常感谢您在回购中发布示例!
@RafaelaLourenço 我很高兴您发现我的回答很有帮助!谢谢!
G
GaétanZ

更新 iOS 15

在 iOS 15 中,您现在可以使用原生 UISheetPresentationController

if let sheet = viewControllerToPresent.sheetPresentationController {
    sheet.detents = [.medium(), .large()]
    // your sheet setup
}
present(viewControllerToPresent, animated: true, completion: nil)

请注意,您甚至可以使用 overcurrentcontext 演示模式重现其导航堆栈:

let nextViewControllerToPresent: UIViewController = ...
nextViewControllerToPresent.modalPresentationStyle = .overCurrentContext
viewControllerToPresent.present(nextViewControllerToPresent, animated: true, completion: nil)

遗产

我根据下面的回答发布了 library

它模仿快捷方式应用程序覆盖。有关详细信息,请参阅 this article

库的主要组件是 OverlayContainerViewController。它定义了一个可以上下拖动视图控制器、隐藏或显示其下方内容的区域。

let contentController = MapsViewController()
let overlayController = SearchViewController()

let containerController = OverlayContainerViewController()
containerController.delegate = self
containerController.viewControllers = [
    contentController,
    overlayController
]

window?.rootViewController = containerController

实施 OverlayContainerViewControllerDelegate 以指定希望的缺口数:

enum OverlayNotch: Int, CaseIterable {
    case minimum, medium, maximum
}

func numberOfNotches(in containerViewController: OverlayContainerViewController) -> Int {
    return OverlayNotch.allCases.count
}

func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                    heightForNotchAt index: Int,
                                    availableSpace: CGFloat) -> CGFloat {
    switch OverlayNotch.allCases[index] {
        case .maximum:
            return availableSpace * 3 / 4
        case .medium:
            return availableSpace / 2
        case .minimum:
            return availableSpace * 1 / 4
    }
}

SwiftUI (12/29/20)

该库的 SwiftUI version 现在可用。

Color.red.dynamicOverlay(Color.green)

上一个答案

我认为建议的解决方案中没有处理一个重要的点:滚动和翻译之间的过渡。

https://i.stack.imgur.com/BqlYi.gif

在 Maps 中,您可能已经注意到,当 tableView 到达 contentOffset.y == 0 时,底部工作表要么向上滑动,要么向下滑动。

这一点很棘手,因为当我们的平移手势开始翻译时,我们不能简单地启用/禁用滚动。它会停止滚动,直到新的触摸开始。此处提出的大多数解决方案都是这种情况。

这是我实施这一议案的尝试。

起点:地图应用

为了开始我们的研究,让我们可视化 Maps 的视图层次结构(在模拟器上启动 Maps 并在 Xcode 9 中选择 Debug > Attach to process by PID or Name > Maps)。

https://i.stack.imgur.com/0qlJx.png

它没有说明动作是如何工作的,但它帮助我理解了它的逻辑。您可以使用 lldb 和视图层次结构调试器。

我们的视图控制器堆栈

让我们创建 Maps ViewController 架构的基本版本。

我们从 BackgroundViewController(我们的地图视图)开始:

class BackgroundViewController: UIViewController {
    override func loadView() {
        view = MKMapView()
    }
}

我们将 tableView 放在专用的 UIViewController 中:

class OverlayViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    lazy var tableView = UITableView()

    override func loadView() {
        view = tableView
        tableView.dataSource = self
        tableView.delegate = self
    }

    [...]
}

现在,我们需要一个 VC 来嵌入覆盖并管理它的翻译。为了简化问题,我们认为它可以将叠加层从一个静态点 OverlayPosition.maximum 转换到另一个 OverlayPosition.minimum

目前它只有一个公共方法来动画位置变化,它有一个透明的视图:

enum OverlayPosition {
    case maximum, minimum
}

class OverlayContainerViewController: UIViewController {

    let overlayViewController: OverlayViewController
    var translatedViewHeightContraint = ...

    override func loadView() {
        view = UIView()
    }

    func moveOverlay(to position: OverlayPosition) {
        [...]
    }
}

最后,我们需要一个 ViewController 来嵌入所有内容:

class StackViewController: UIViewController {

    private var viewControllers: [UIViewController]

    override func viewDidLoad() {
        super.viewDidLoad()
        viewControllers.forEach { gz_addChild($0, in: view) }
    }
}

在我们的 AppDelegate 中,我们的启动顺序如下所示:

let overlay = OverlayViewController()
let containerViewController = OverlayContainerViewController(overlayViewController: overlay)
let backgroundViewController = BackgroundViewController()
window?.rootViewController = StackViewController(viewControllers: [backgroundViewController, containerViewController])

叠加翻译背后的难点

现在,如何翻译我们的叠加层?

大多数提议的解决方案都使用专用的平移手势识别器,但实际上我们已经有了一个:表格视图的平移手势。此外,我们需要保持滚动和翻译同步,UIScrollViewDelegate 包含我们需要的所有事件!

一个简单的实现会使用第二个平移手势并在转换发生时尝试重置表格视图的 contentOffset

func panGestureAction(_ recognizer: UIPanGestureRecognizer) {
    if isTranslating {
        tableView.contentOffset = .zero
    }
}

但它不起作用。当它自己的平移手势识别器动作触发或调用它的 displayLink 回调时,tableView 会更新它的 contentOffset。我们的识别器不可能在成功覆盖 contentOffset 之后立即触发。我们唯一的机会是参与布局阶段(通过在滚动视图的每一帧覆盖滚动视图调用的 layoutSubviews)或响应每次 contentOffset 调用的委托的 didScroll 方法被修改。让我们试试这个。

翻译实施

我们向 OverlayVC 添加一个委托,以将滚动视图的事件分派给我们的翻译处理程序 OverlayContainerViewController

protocol OverlayViewControllerDelegate: class {
    func scrollViewDidScroll(_ scrollView: UIScrollView)
    func scrollViewDidStopScrolling(_ scrollView: UIScrollView)
}

class OverlayViewController: UIViewController {

    [...]

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        delegate?.scrollViewDidScroll(scrollView)
    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        delegate?.scrollViewDidStopScrolling(scrollView)
    }
}

在我们的容器中,我们使用枚举来跟踪翻译:

enum OverlayInFlightPosition {
    case minimum
    case maximum
    case progressing
}

当前位置计算如下所示:

private var overlayInFlightPosition: OverlayInFlightPosition {
    let height = translatedViewHeightContraint.constant
    if height == maximumHeight {
        return .maximum
    } else if height == minimumHeight {
        return .minimum
    } else {
        return .progressing
    }
}

我们需要 3 种方法来处理翻译:

第一个告诉我们是否需要开始翻译。

private func shouldTranslateView(following scrollView: UIScrollView) -> Bool {
    guard scrollView.isTracking else { return false }
    let offset = scrollView.contentOffset.y
    switch overlayInFlightPosition {
    case .maximum:
        return offset < 0
    case .minimum:
        return offset > 0
    case .progressing:
        return true
    }
}

第二个执行翻译。它使用 scrollView 的平移手势的 translation(in:) 方法。

private func translateView(following scrollView: UIScrollView) {
    scrollView.contentOffset = .zero
    let translation = translatedViewTargetHeight - scrollView.panGestureRecognizer.translation(in: view).y
    translatedViewHeightContraint.constant = max(
        Constant.minimumHeight,
        min(translation, Constant.maximumHeight)
    )
}

当用户松开手指时,第三个动画结束翻译。我们使用速度和视图的当前位置来计算位置。

private func animateTranslationEnd() {
    let position: OverlayPosition =  // ... calculation based on the current overlay position & velocity
    moveOverlay(to: position)
}

我们的叠加层的委托实现看起来像:

class OverlayContainerViewController: UIViewController {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard shouldTranslateView(following: scrollView) else { return }
        translateView(following: scrollView)
    }

    func scrollViewDidStopScrolling(_ scrollView: UIScrollView) {
        // prevent scroll animation when the translation animation ends
        scrollView.isEnabled = false
        scrollView.isEnabled = true
        animateTranslationEnd()
    }
}

最后一个问题:调度覆盖容器的触摸

现在翻译效率很高。但是还有最后一个问题:触摸没有传递到我们的背景视图。它们都被覆盖容器的视图拦截。我们不能将 isUserInteractionEnabled 设置为 false,因为它也会禁用我们表格视图中的交互。该解决方案是地图应用程序中大量使用的解决方案,PassThroughView

class PassThroughView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

它将自己从响应者链中移除。

OverlayContainerViewController 中:

override func loadView() {
    view = PassThroughView()
}

结果

结果如下:

https://i.stack.imgur.com/fihVW.gif

您可以找到代码 here

如果您发现任何错误,请告诉我!请注意,您的实现当然可以使用第二个平移手势,特别是如果您在叠加层中添加标题。

更新 23/08/18

当用户结束拖动时,我们可以将 scrollViewDidEndDragging 替换为 willEndScrollingWithVelocity 而不是 enabling/disabling 滚动:

func scrollView(_ scrollView: UIScrollView,
                willEndScrollingWithVelocity velocity: CGPoint,
                targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    switch overlayInFlightPosition {
    case .maximum:
        break
    case .minimum, .progressing:
        targetContentOffset.pointee = .zero
    }
    animateTranslationEnd(following: scrollView)
}

我们可以使用弹簧动画并在动画制作时允许用户交互以使运动流更好:

func moveOverlay(to position: OverlayPosition,
                 duration: TimeInterval,
                 velocity: CGPoint) {
    overlayPosition = position
    translatedViewHeightContraint.constant = translatedViewTargetHeight
    UIView.animate(
        withDuration: duration,
        delay: 0,
        usingSpringWithDamping: velocity.y == 0 ? 1 : 0.6,
        initialSpringVelocity: abs(velocity.y),
        options: [.allowUserInteraction],
        animations: {
            self.view.layoutIfNeeded()
    }, completion: nil)
}

这种方法在动画阶段不是交互式的。例如,在地图中,我可以用手指抓住正在展开的工作表。然后我可以通过向上或向下平移来擦洗动画。它将返回到最近的任何位置。我相信这可以使用 UIViewPropertyAnimator(具有暂停功能)来解决,然后使用 fractionComplete 属性执行清理。
你是对的@aust。我忘了推送我之前的更改。现在应该很好了。谢谢。
这是一个好主意,但是我可以立即看到一个问题。在 translateView(following:) 方法中,您根据平移计算高度,但它可以从不同于 0.0 的值开始 (例如,表格视图会滚动一点,当您开始拖动时,覆盖会最大化)。这将导致最初的跳跃。您可以通过 scrollViewWillBeginDragging 回调解决此问题,您可以在其中记住表格视图的初始 contentOffset 并在 translateView(following:) 的计算中使用它。
@GaétanZ 我也有兴趣在模拟器上将调试器附加到 Maps.app,但收到错误“无法附加到 pid:“9276””,然后是“确保“地图”尚未运行,并且 <用户名> 有权调试它。” - 你是如何欺骗 Xcode 以允许附加到 Maps.app 的?
@matm @GaétanZ 在 Xcode 10.1/Mojave 上为我工作->只需禁用即可禁用系统完整性保护 (SIP)。您需要: 1) 重新启动您的 mac; 2)按住cmd+R; 3) 从菜单中选择实用程序,然后选择终端; 4) 在终端窗口中输入:csrutil disable && reboot。然后,您将拥有 root 通常应该赋予您的所有权力,包括能够附加到 Apple 进程。
R
Rajee Jones

试试 Pulley

Pulley 是一个易于使用的抽屉库,旨在模仿 iOS 10 的地图应用程序中的抽屉。它公开了一个简单的 API,允许您使用任何 UIViewController 子类作为抽屉内容或主要内容。

https://i.imgur.com/bmEWqy7.gif

https://github.com/52inc/Pulley


它是否支持在底层列表中滚动?
@RichardLindhout - 运行演示后,它看起来支持滚动,但不支持从移动抽屉到滚动列表的平滑过渡。
该示例似乎在左下角隐藏了 Apple 的合法链接。
超级简单的问题,你是怎么得到底部表格顶部的指示器的?
如果有人对我的项目感兴趣,我已经用 C# 重写了 Pulley <github.com/iGeX/Xamarin.Pulley.Ios>。我不想处理过时的绑定库并决定将其转换为纯 Xamarin.Ios C# 代码,这样我就可以轻松地对其进行更改和升级。
u
ugur

我编写了自己的库来实现 ios Maps 应用程序中的预期行为。这是一个面向协议的解决方案。因此,您不需要继承任何基类,而是创建一个工作表控制器并根据需要进行配置。它还支持带有或不带有 UINavigationController 的内部导航/演示。

有关更多详细信息,请参见下面的链接。

https://github.com/OfTheWolf/UBottomSheet

https://github.com/OfTheWolf/UBottomSheet/raw/master/records/record1.gif


这应该是选定的答案,因为您仍然可以单击弹出窗口后面的 VC。
超级简单的问题,你是怎么得到底部表格顶部的指示器的?
s
scenee

你可以试试我的回答https://github.com/SCENEE/FloatingPanel。它提供了一个容器视图控制器来显示一个“底页”界面。

它易于使用,您不介意任何手势识别器处理!如果需要,您还可以在底部工作表中跟踪滚动视图(或同级视图)。

这是一个简单的例子。请注意,您需要准备一个视图控制器以在底部工作表中显示您的内容。

import UIKit
import FloatingPanel

class ViewController: UIViewController {
    var fpc: FloatingPanelController!

    override func viewDidLoad() {
        super.viewDidLoad()

        fpc = FloatingPanelController()

        // Add "bottom sheet" in self.view.
        fpc.add(toParent: self)

        // Add a view controller to display your contents in "bottom sheet".
        let contentVC = ContentViewController()
        fpc.set(contentViewController: contentVC)

        // Track a scroll view in "bottom sheet" content if needed.
        fpc.track(scrollView: contentVC.tableView)    
    }
    ...
}

Here 是另一个示例代码,用于显示底部表格以搜索 Apple 地图等位置。

https://i.stack.imgur.com/a2sEB.gif


fpc.set(contentViewController: newsVC) ,库中缺少方法
超级简单的问题,你是怎么得到底部表格顶部的指示器的?
@AIsrafil 我认为它只是一个 UIView
我认为这是一个非常好的解决方案,但是在 iOS 13 中存在一个问题,即使用这个浮动面板呈现模态视图控制器。
昨天尝试过,但不喜欢:选项过多,非标准委托函数命名约定过于复杂(使用 vcfps,两者都应该是 floatingPanel),地图示例显示面板跳转方式向下拖动时短暂向上,示例代码很混乱。
p
pkamb

2021 年的 iOS 15 增加了 UISheetPresentationController,这是 Apple 首次公开发布 Apple 地图样式的“底页”:

UISheetPresentationController UISheetPresentationController 允许您将视图控制器呈现为工作表。在展示您的视图控制器之前,请使用您想要的工作表的行为和外观配置其工作表展示控制器。工作表显示控制器根据卡位指定工作表的大小,即工作表自然放置的高度。定位器允许纸张从其完全展开的框架的一个边缘调整大小,而其他三个边缘保持固定。您可以使用 detents 指定工作表支持的 detent,并使用 selectedDetentIdentifier 监视其最近选择的 detent。 https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller

在 WWDC Session 10063 中探索了这个新的底部表单控件:Customize and Resize Sheets in UIKit

https://i.stack.imgur.com/oh9Qi.jpg

很遗憾....

在 iOS 15 中,UISheetPresentationController 仅使用 mediumlarge 定位器启动。

iOS 15 API 中明显没有 small 定位,这将需要显示始终呈现的“折叠”底页,如 Apple 地图:

在 UISheetPresentationController 中自定义较小的 Detents?

medium 定位器已发布以处理诸如共享表或邮件中的“••• 更多”菜单之类的用例:按钮触发的半页。

在 iOS 15 中,Apple Maps 现在正在使用这个 UIKit 表单演示而不是自定义实现,这是朝着正确方向迈出的一大步。 iOS 15 中的 Apple 地图继续显示“小”条以及 1/3 高度条。但是这些视图大小不是开发人员可用的公共 API。

WWDC 2021 实验室的 UIKit 工程师似乎知道 small detent 将是一个非常受欢迎的 UIKit 组件。我预计明年会看到 iOS 16 的 API 扩展。


从 iOS 16 开始,现在有一个用于 UISheetPresentationController 的 custom detent。迷你示例代码:.custom(identifier: .small) { context in 0.3 * context.maximumDetentValue }
V
Varma Mukesh

**适用于此的 iOS 15 本机支持**

@IBAction func openSheet() { 
        let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController")
        // Create the view controller.
        if #available(iOS 15.0, *) {
            let formNC = UINavigationController(rootViewController: secondVC!)
            formNC.modalPresentationStyle = UIModalPresentationStyle.pageSheet
            guard let sheetPresentationController = formNC.presentationController as? UISheetPresentationController else {
                return
            }
            sheetPresentationController.detents = [.medium(), .large()]
            sheetPresentationController.prefersGrabberVisible = true
            present(formNC, animated: true, completion: nil)
        } else {
            // Fallback on earlier versions
        }
   }

https://i.stack.imgur.com/j7INF.gif


n
ndominati2

我们刚刚发布了一个支持 iOS 11.4+ 的纯 Swift 包,它为您提供了一个带有可以自定义的主题和行为选项的 BottomSheet。该组件易于使用且灵活。您可以在此处找到它:https://github.com/LunabeeStudio/LBBottomSheet。此存储库中也提供了一个演示项目。

例如,它支持不同的方式来管理所需的高度,并且还为它背后的控制器增加了检测高度变化和适应其底部内容插图的能力。

您可以在 GitHub 存储库和文档中找到更多信息:https://lbbottomsheet.lunabee.studio

我认为它可以帮助你做你正在寻找的东西。如果您有意见/问题,请随时告诉我 :)

在这里,您可以看到所有可能的 BottomSheet 配置之一:

https://i.stack.imgur.com/sJufJ.png


为什么抓斗手柄在不规范的地方? (对我来说立即红旗。)
您可以完全自定义抓取器的位置。如果需要,您可以选择其顶部插图以使其更靠近顶部。此屏幕截图只是您可以执行的操作的一个示例。
好的,这有帮助 :-) 但是为什么这应该是可配置的?我认为人们应该坚持苹果的做法,也许可以选择隐藏抓取器。像这样的轻微变体会产生潜意识的噪音,让用户感觉有些东西是错误的/奇怪的。我们都知道添加功能、选项和使事物可配置是程序员的常见陷阱;-)
做得很好,实施只需要 1/2 小时
M
Moshe Gutman

iOS 15 终于添加了原生 UISheetPresentationController

官方文档https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller


L
Luca Iaco

我最近创建了一个名为 SwipeableView 的组件作为 UIView 的子类,用 Swift 5.1 编写。它支持所有 4 个方向,具有多个自定义选项,并且可以动画和插入不同的属性和项目(例如布局约束、背景/色调颜色、仿射变换、Alpha 通道和视图中心,所有这些都在各自的展示案例中进行了演示)。如果设置或自动检测到,它还支持与内部滚动视图的滑动协调。使用起来应该非常简单直接(我希望🙂)

https://github.com/LucaIaco/SwipeableView 处的链接

概念证明:

https://i.stack.imgur.com/BDiCX.gif

希望能帮助到你


d
day

也许你可以试试我的答案 https://github.com/AnYuan/AYPannel,灵感来自 Pulley。从移动抽屉到滚动列表的平滑过渡。我在容器滚动视图上添加了一个平移手势,并将 shouldRecognizeSimultaneouslyWithGestureRecognizer 设置为返回 YES。更多细节在我上面的 github 链接中。希望提供帮助。


虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。
k
kenfai

如果您正在寻找使用 View Struct 的 SwiftUI 2.0 解决方案,这里是:

https://github.com/kenfai/KavSoft-Tutorials-iOS/tree/main/MapsBottomSheet

https://i.stack.imgur.com/5xNeL.gif