Skip to content

Instantly share code, notes, and snippets.

@BestKora
Last active March 21, 2020 18:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BestKora/2b28953f5f1a52a18126af5c0ae4ade5 to your computer and use it in GitHub Desktop.
Save BestKora/2b28953f5f1a52a18126af5c0ae4ade5 to your computer and use it in GitHub Desktop.
Control vertical move of the ScrollView in SwiftUI.
import SwiftUI
import Combine
public struct BottomSheet<Content>: View where Content : View {
/// The content of the scroll view.
public var content: Content
/// The offset of the scrollview updated as the scroll view scrolls
@Binding public var offset : CGSize
@State private var offsetContent: CGPoint = .zero
@State private var accumulated = CGSize.zero
@State private var moveScroll = true
private var currentOffsetPoint: AnyPublisher<CGPoint, Never> {
Just(self.offsetContent)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
public init(offset: Binding<CGSize> = .constant(.zero), @ViewBuilder content: () -> Content) {
self._offset = offset
self.content = content()
}
// Declares the content and behavior of this view.
public var body: some View {
GeometryReader { geometry in
VStack {
if self.moveScroll {
OffsetScrollView(offset: self.$offsetContent) {
self.content
} /// scroll
.offset(self.offset)
.gesture( DragGesture(minimumDistance: 0)
.onChanged ({value in
if self.accumulated == CGSize.zero {
self.accumulated = self.offset
}
let localOffset = value.translation.height + self.accumulated.height
self.offset = CGSize(width: 0,
height: min(max(localOffset,10),(geometry.size.height - 20)))
if self.offset.height <= 10 {self.moveScroll = false}
})
.onEnded ({ value in
let localOffset = value.translation.height + self.accumulated.height
self.accumulated = CGSize(width: 0,
height: min(max(localOffset,10),(geometry.size.height - 20)))
}
)) /// gesture
//-----------------
} else {
OffsetScrollView(offset: self.$offsetContent) {
self.content
} /// scroll
.offset(self.offset)
.onReceive(self.currentOffsetPoint) { point in
if !self.moveScroll {
if point.y >= 10 {
self.moveScroll = true
self.accumulated = CGSize(width: 0, height: point.y)
self.offset = CGSize(width: 0, height: point.y)
}
}
} /// on receive
} // else
} /// vstack
} // geometry
}
}
import SwiftUI
struct ContentView: View {
@State var restaurant = restaurants[0]
var body: some View {
ZStack {
VStack {
List {
ForEach(restaurants) { restaurant in
BasicImageRow(restaurant: restaurant)
.onTapGesture {
self.restaurant = restaurant
}
.background(self.restaurant == restaurant ? Color.gray .opacity(0.3) : Color.white)
}
}
}
RestaurantDetailView1(offset: CGSize(width: 0, height: 500), restaurant: restaurant)
}
}
}
struct BasicImageRow: View {
var restaurant: Restaurant
var body: some View {
HStack {
Image(restaurant.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(restaurant.name)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Created by Zac White on 9/30/19.
// Copyright © 2019 Zac White. All rights reserved.
//https://zacwhite.com/2019/scrollview-content-offsets-swiftui/
import SwiftUI
public struct OffsetScrollView<Content>: View where Content : View {
/// The content of the scroll view.
public var content: Content
/// The scrollable axes.
///
/// The default is `.vertical`.
public var axes: Axis.Set
/// If true, the scroll view may indicate the scrollable component of
/// the content offset, in a way suitable for the platform.
///
/// The default is `true`.
public var showsIndicators: Bool
/// The initial offset in the global frame, used for calculating the relative offset
@State private var initialOffset: CGPoint? = nil
/// The offset of the scrollview updated as the scroll view scrolls
@Binding public var offset: CGPoint
public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, offset: Binding<CGPoint> = .constant(.zero), @ViewBuilder content: () -> Content) {
self.axes = axes
self.showsIndicators = showsIndicators
self._offset = offset
self.content = content()
}
/// observe offset
func makeView(geometry: GeometryProxy) -> some View {
DispatchQueue.main.async {
let globalOrigin = geometry.frame(in: .global).origin
self.initialOffset = self.initialOffset ?? globalOrigin
let initialOffset = (self.initialOffset ?? .zero)
let offset = CGPoint(x: globalOrigin.x - initialOffset.x,
y: globalOrigin.y - initialOffset.y)
self.offset = offset
}
return AnyView(EmptyView())
}
/// Declares the content and behavior of this view.
public var body: some View {
ScrollView(axes, showsIndicators: showsIndicators) {
VStack(alignment: .leading, spacing: 0) {
GeometryReader { geometry in
self.makeView(geometry: geometry)
}.frame(width: 0, height: 0)
content
}
}
}
}
import Foundation
struct Restaurant: Identifiable {
var id: UUID = UUID()
var name: String
var type: String
var location: String
var phone: String
var description: String
var image: String
var isVisited: Bool
init(name: String, type: String, location: String, phone: String, description: String, image: String, isVisited: Bool) {
self.name = name
self.type = type
self.location = location
self.phone = phone
self.description = description
self.image = image
self.isVisited = isVisited
}
init() {
self.init(name: "", type: "", location: "", phone: "", description: "", image: "", isVisited: false)
}
}
extension Restaurant{
static func == (lhs: Restaurant, rhs: Restaurant) -> Bool{
return lhs.id == rhs.id
}
}
#if DEBUG
var restaurants: [Restaurant] = [
Restaurant(name: "Cafe Deadend", type: "Coffee & Tea Shop", location: "G/F, 72 Po Hing Fong, Sheung Wan, Hong Kong", phone: "232-923423", description: "Searching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.\n\nA little gem hidden at the corner of the street is nothing but fantastic! This place is warm and cozy. We open at 7 every morning except Sunday, and close at 9 PM. We offer a variety of coffee drinks and specialties including lattes, cappuccinos, teas, and more. We serve breakfast, lunch, and dinner in an airy open setting. Come over, have a coffee and enjoy a chit-chat with our baristas.\n\nA great cafe in Austrian style. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. We also serve breakfast and light lunch. Come over to enjoy the elegant ambience and quality coffee.", image: "cafedeadend", isVisited: false),
Restaurant(name: "Homei", type: "Cafe", location: "Shop B, G/F, 22-24A Tai Ping San Street SOHO, Sheung Wan, Hong Kong", phone: "348-233423", description: "A little gem hidden at the corner of the street is nothing but fantastic! This place is warm and cozy. We open at 7 every morning except Sunday, and close at 9 PM. We offer a variety of coffee drinks and specialties including lattes, cappuccinos, teas, and more. We serve breakfast, lunch, and dinner in an airy open setting. Come over, have a coffee and enjoy a chit-chat with our baristas.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "homei", isVisited: false),
Restaurant(name: "Teakha", type: "Tea House", location: "Shop B, 18 Tai Ping Shan Road SOHO, Sheung Wan, Hong Kong", phone: "354-243523", description: "Take a moment to stop and smell tealeaves! We are about the community, our environment, and all things created by the warmth of our hands. We open at 11 AM, and close at 7 PM. At teakha, we sell only the best single-origin teas sourced by our sister company Plantation directly from small tea plantations. The teas are then either cooked to perfection with milk in a pot or brewed.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "teakha", isVisited: false),
Restaurant(name: "Cafe loisl", type: "Austrian / Causual Drink", location: "Shop B, 20 Tai Ping Shan Road SOHO, Sheung Wan, Hong Kong", phone: "453-333423", description: "A great cafe in Austrian style. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. We also serve breakfast and light lunch. Come over to enjoy the elegant ambience and quality coffee.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "cafeloisl", isVisited: false),
Restaurant(name: "Petite Oyster", type: "French", location: "24 Tai Ping Shan Road SOHO, Sheung Wan, Hong Kong", phone: "983-284334", description: "An upscale dining venue, features premium and seasonal imported oysters, and delicate yet creative modern European cuisines. Its oyster bar displays a full array of freshest oysters imported from all over the world including France, Australia, USA and Japan.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "petiteoyster", isVisited: false),
Restaurant(name: "For Kee Restaurant", type: "Hong Kong", location: "Shop J-K., 200 Hollywood Road, SOHO, Sheung Wan, Hong Kong", phone: "232-434222", description: "A great local cafe for breakfast and lunch! Located in a quiet area in Sheung Wan, we offer pork chop buns, HK french toast, and many more. We open from 7 AM to 4:30 PM.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "forkeerestaurant", isVisited: false),
Restaurant(name: "Po's Atelier", type: "Bakery", location: "G/F, 62 Po Hing Fong, Sheung Wan, Hong Kong", phone: "234-834322", description: "A boutique bakery focusing on artisan breads and pastries paired with inspiration from Japan and Scandinavia. We are crazy about bread and excited to share our artisan bakes with you. We open at 10 every morning, and close at 7 PM.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "posatelier", isVisited: false),
Restaurant(name: "Bourke Street Backery", type: "Chocolate", location: "633 Bourke St Sydney New South Wales 2010 Surry Hills", phone: "982-434343", description: "We make everything by hand with the best possible ingredients, from organic sourdoughs to pastries and cakes. A combination of great produce, good strong coffee, artisanal skill and hard work creates the honest, soulful, delectable bites that have made Bourke Street Bakery famous. Visit us at one of our many Sydney locations!\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "bourkestreetbakery", isVisited: false),
Restaurant(name: "Haigh's Chocolate", type: "Cafe", location: "412-414 George St Sydney New South Wales", phone: "734-232323", description: "Haigh's Chocolates is Australia's oldest family owned chocolate maker. We have been making chocolate in Adelaide, South Australia since 1915 and we are committed to the art of premium chocolate making from the cocoa bean. Our chocolates are always presented to perfection in our own retail stores. Visit any one of them and you'll find chocolate tasting, gift wrapping and personalised attention are all part of the service.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "haighschocolate", isVisited: false),
Restaurant(name: "Palomino Espresso", type: "American / Seafood", location: "Shop 1 61 York St Sydney New South Wales", phone: "872-734343", description: "We offer an assortment of on-site baked goods and sandwiches. This place has always been a favourite among office workers. We open at 7 every morning including Sunday, and close at 4 PM. Come over, have a coffee and enjoy a chit-chat with our baristas.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "palominoespresso", isVisited: false),
Restaurant(name: "Upstate", type: "Seafood", location: "95 1st Ave New York, NY 10003", phone: "343-233221", description: "The absolute best seafood place in town. The atmosphere here creates a very homey feeling. We open at 5 PM, and close at 10:30 PM. ", image: "upstate", isVisited: false),
Restaurant(name: "Traif", type: "American", location: "229 S 4th St Brooklyn, NY 11211", phone: "985-723623", description: "A young crowd populates this pork-focused American eatery in an older Williamsburg neighborhood. We open at 6PM, and close at 11 PM. If you enjoy a shared small plates dinner experience, come over and have a try.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "traif", isVisited: false),
Restaurant(name: "Graham Avenue Meats", type: "Breakfast & Brunch", location: "445 Graham Ave Brooklyn, NY 11211", phone: "455-232345", description: "Classic Italian deli & butcher draws patrons with meat-filled submarine sandwiches. We use the freshest meats and veggies to create the perfect panini for you. We look forward to seeing you!\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "grahamavenuemeats", isVisited: false),
Restaurant(name: "Waffle & Wolf", type: "Coffee & Tea", location: "413 Graham Ave Brooklyn, NY 11211", phone: "434-232322", description: "Small shop, some seating, definitely something different! We open at 7 every morning except Sunday, and close at 9 PM. We offer a variety of waffles with choices of sweet & savory fillings. If you are gluten free and craving waffles, this is the place to go.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "wafflewolf", isVisited: false),
Restaurant(name: "Five Leaves", type: "Bistro", location: "18 Bedford Ave Brooklyn, NY 11222", phone: "343-234553", description: " Great food, cocktails, ambiance, service. Nothing beats having a warm dinner and a whiskey. We open at 8 every morning, and close at 1 AM. The ricotta pancakes are something you must try.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "fiveleaves", isVisited: false),
Restaurant(name: "Cafe Lore", type: "Latin American", location: "Sunset Park 4601 4th Ave Brooklyn, NY 11220", phone: "342-455433", description: "Good place, great environment and amazing food! We open at 10 every morning except Sunday, and close at 9 PM. Give us a visit! Enjoy mushroom raviolis, pumpkin raviolis, meat raviolis with sausage and peppers, pork cutlets, eggplant parmesan, and salad.", image: "cafelore", isVisited: false),
Restaurant(name: "Confessional", type: "Spanish", location: "308 E 6th St New York, NY 10003", phone: "643-332323", description: "Most delicious cocktail you would ever try! Our menu includes a wide range of high quality internationally inspired dishes, vegetarian options, and local cuisine. We open at 10 AM, and close at 10 PM. We welcome you into our place to enjoy a meal with your friends.", image: "confessional", isVisited: false),
Restaurant(name: "Barrafina", type: "Spanish", location: "54 Frith Street London W1D 4SL United Kingdom", phone: "542-343434", description: "a collection of authentic Spanish Tapas bars in Central London! We have an open kitchen, a beautiful marble-topped bar where guests can sit and watch the chefs at work and stylish red leather stools. Lunch starts at 1 PM. Dinners starts 5:30 PM.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "barrafina", isVisited: false),
Restaurant(name: "Donostia", type: "Spanish", location: "10 Seymour Place London W1H 7ND United Kingdom", phone: "722-232323", description: "Very good basque food, creative dishes with terrific flavors! Donostia is a high end tapas restaurant with a friendly relaxed ambiance. Come over to enjoy awesome tapas!\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "donostia", isVisited: false),
Restaurant(name: "Royal Oak", type: "British", location: "2 Regency Street London SW1P 4BZ United Kingdom", phone: "343-988834", description: "Specialise in great pub food. Established in 1872, we have local and world lagers, craft beer and a selection of wine and spirits for people to enjoy. Don't forget to try our range of Young's Ales and Fish and Chips.\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "royaloak", isVisited: false),
Restaurant(name: "CASK Pub and Kitchen", type: "Thai", location: "22 Charlwood Street London SW1V 2DY Pimlico", phone: "432-344050", description: "With kitchen serving gourmet burgers. We offer food every day of the week, Monday through to Sunday. Join us every Sunday from 4:30 – 7:30pm for live acoustic music!\n\nSearching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "caskpubkitchen", isVisited: false)
]
#endif
import SwiftUI
struct RestaurantDetailView1: View {
@State var offset = CGSize.zero
let restaurant: Restaurant
var body: some View {
BottomSheet(offset: self.$offset) {
TitleBar()
.background(Color.gray)
HeaderView(restaurant: self.restaurant)
DetailInfoView(icon: "map", info: self.restaurant.location)
.padding(.top)
.background(Color.white)
DetailInfoView(icon: "phone", info: self.restaurant.phone)
.background(Color.white)
DetailInfoView(icon: nil, info: self.restaurant.description)
.padding(.top)
.background(Color.white)
DetailInfoView(icon: nil, info: self.restaurant.description)
.padding(.top)
.background(Color.white)
} // sheet
} // body
}
struct RestaurantDetailView1_Previews: PreviewProvider {
static var previews: some View {
RestaurantDetailView1(offset: CGSize(width: 0, height: 300), restaurant: restaurants[0])
.background(Color.black.opacity(0.3))
}
}
struct HandleBar: View {
var body: some View {
Rectangle()
.frame(width: 50, height: 5)
.foregroundColor(Color(.systemGray5))
.cornerRadius(10)
}
}
struct TitleBar: View {
var body: some View {
HStack {
Text("Restaurant Details")
.font(.headline)
.foregroundColor(.primary)
Spacer()
}
.padding()
}
}
struct HeaderView: View {
let restaurant: Restaurant
var body: some View {
Image(restaurant.image)
.resizable()
.scaledToFill()
.frame(height: 300)
.clipped()
.overlay(
HStack {
VStack(alignment: .leading) {
Spacer()
Text(restaurant.name)
.foregroundColor(.white)
.font(.system(.largeTitle, design: .rounded))
.bold()
Text(restaurant.type)
.font(.system(.headline, design: .rounded))
.padding(5)
.foregroundColor(.white)
.background(Color.red)
.cornerRadius(5)
}
Spacer()
}
.padding()
)
}
}
struct DetailInfoView: View {
let icon: String?
let info: String
var body: some View {
HStack {
if icon != nil {
Image(systemName: icon!)
.padding(.trailing, 10)
}
Text(info)
.font(.system(.body, design: .rounded))
Spacer()
}.padding(.horizontal)
}
}
@BestKora
Copy link
Author

BestKora commented Mar 21, 2020

Example from book Mastering SwiftUI

Scroll Up

Scroll Up

Scroll Up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment