Skip to content

Instantly share code, notes, and snippets.

@niaeashes
Last active March 4, 2022 11:54
Show Gist options
  • Select an option

  • Save niaeashes/d8f7f17c56f2ccd444032b0145b62283 to your computer and use it in GitHub Desktop.

Select an option

Save niaeashes/d8f7f17c56f2ccd444032b0145b62283 to your computer and use it in GitHub Desktop.

Revisions

  1. niaeashes revised this gist Mar 4, 2022. 1 changed file with 32 additions and 26 deletions.
    58 changes: 32 additions & 26 deletions TooltipTest.swift
    Original file line number Diff line number Diff line change
    @@ -1,19 +1,5 @@
    import SwiftUI

    struct SizeKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
    value = nextValue()
    }
    }

    struct RectKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
    value = nextValue()
    }
    }

    struct OffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = .zero
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    @@ -23,10 +9,7 @@ struct OffsetKey: PreferenceKey {

    struct ContentView: View {

    @State var size: CGSize? = nil

    @Namespace var scrollId
    @Namespace var contentId

    var body: some View {
    ScrollView {
    @@ -39,13 +22,7 @@ struct ContentView: View {
    .padding()
    .compositingGroup()
    }
    .background(GeometryReader { geometry in
    Color.clear
    .preference(key: RectKey.self, value: geometry.frame(in: .named(contentId)))
    .onPreferenceChange(RectKey.self) {
    proxy.focus(frame: $0)
    }
    })
    .modifier(proxy.capturer)
    ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() }
    }
    GeometryReader { geometry in
    @@ -57,11 +34,9 @@ struct ContentView: View {
    OverlayView()
    })
    .onPreferenceChange(OffsetKey.self) { _ in
    print(Date(), "Change offset!!!")
    withAnimation { proxy.hide() }
    }
    }
    .coordinateSpace(name: contentId)
    }
    .coordinateSpace(name: scrollId)
    }
    @@ -78,11 +53,13 @@ struct TooltipReader<Content: View>: View {

    var body: some View {
    content(proxy)
    .coordinateSpace(name: proxy.contentId)
    .environmentObject(proxy)
    }
    }

    class TooltipProxy: ObservableObject {

    var isPresented = false {
    willSet {
    if isPresented != newValue {
    @@ -92,6 +69,8 @@ class TooltipProxy: ObservableObject {
    }
    @Published var frame: CGRect = .zero

    var contentId = UUID()

    func focus(frame: CGRect) {
    self.frame = frame
    }
    @@ -107,6 +86,33 @@ class TooltipProxy: ObservableObject {
    func hide() {
    isPresented = false
    }

    var capturer: some ViewModifier {
    FrameCaptureModifier(proxy: self)
    }

    struct RectKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
    value = nextValue()
    }
    }

    struct FrameCaptureModifier: ViewModifier {

    let proxy: TooltipProxy

    func body(content: Content) -> some View {
    content
    .background(GeometryReader { geometry in
    Color.clear
    .preference(key: RectKey.self, value: geometry.frame(in: .named(proxy.contentId)))
    .onPreferenceChange(RectKey.self) {
    proxy.focus(frame: $0)
    }
    })
    }
    }
    }

    struct TooltipModifier<Tooltip: View>: ViewModifier {
  2. niaeashes revised this gist Mar 4, 2022. 1 changed file with 77 additions and 31 deletions.
    108 changes: 77 additions & 31 deletions TooltipTest.swift
    Original file line number Diff line number Diff line change
    @@ -24,64 +24,110 @@ struct OffsetKey: PreferenceKey {
    struct ContentView: View {

    @State var size: CGSize? = nil
    @State var showOverlay = false
    @State var buttonFrame: CGRect = .zero

    @Namespace var scrollId
    @Namespace var contentId

    var body: some View {
    ScrollView {
    ZStack {
    VStack {
    ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() }
    Button(action: { withAnimation { showOverlay.toggle() } }) {
    Text("Hello, world!")
    .padding()
    .compositingGroup()
    TooltipReader { proxy in
    ZStack {
    VStack {
    ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() }
    Button(action: { withAnimation { proxy.toggle() } }) {
    Text("Hello, world!")
    .padding()
    .compositingGroup()
    }
    .background(GeometryReader { geometry in
    Color.clear
    .preference(key: RectKey.self, value: geometry.frame(in: .named(contentId)))
    .onPreferenceChange(RectKey.self) {
    proxy.focus(frame: $0)
    }
    })
    ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() }
    }
    .background(GeometryReader { geometry in
    Color.clear
    .preference(key: RectKey.self, value: geometry.frame(in: .named(contentId)))
    .onPreferenceChange(RectKey.self) {
    buttonFrame = $0
    }
    })
    ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() }
    GeometryReader { geometry in
    Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .named(scrollId)).minY)
    }
    .frame(height: 0)
    }
    GeometryReader { geometry in
    Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .named(scrollId)).minY)
    .modifier(TooltipModifier {
    OverlayView()
    })
    .onPreferenceChange(OffsetKey.self) { _ in
    print(Date(), "Change offset!!!")
    withAnimation { proxy.hide() }
    }
    .frame(height: 0)
    }
    .modifier(TooltipModifier(frame: buttonFrame) {
    OverlayView()
    })
    .coordinateSpace(name: contentId)
    }
    .coordinateSpace(name: scrollId)
    .onPreferenceChange(OffsetKey.self) { _ in
    print(Date(), "Change offset!!!")
    withAnimation { showOverlay = false }
    }
    }

    struct TooltipReader<Content: View>: View {

    let content: (TooltipProxy) -> Content
    @StateObject var proxy = TooltipProxy()

    init(@ViewBuilder content: @escaping (TooltipProxy) -> Content) {
    self.content = content
    }

    var body: some View {
    content(proxy)
    .environmentObject(proxy)
    }
    }

    class TooltipProxy: ObservableObject {
    var isPresented = false {
    willSet {
    if isPresented != newValue {
    objectWillChange.send()
    }
    }
    }
    @Published var frame: CGRect = .zero

    func focus(frame: CGRect) {
    self.frame = frame
    }

    func toggle() {
    isPresented.toggle()
    }

    func show() {
    isPresented = true
    }

    func hide() {
    isPresented = false
    }
    }

    struct TooltipModifier<Tooltip: View>: ViewModifier {

    let tooltip: Tooltip
    let frame: CGRect

    init(frame: CGRect, @ViewBuilder tooltip: () -> Tooltip) {
    self.frame = frame
    @EnvironmentObject var proxy: TooltipProxy

    init(@ViewBuilder tooltip: () -> Tooltip) {
    self.tooltip = tooltip()
    }

    var frame: CGRect { proxy.frame }

    func body(content: Content) -> some View {
    ZStack {
    content
    tooltip
    .position(x: frame.midX, y: frame.midY - frame.height - 16)
    if proxy.isPresented {
    tooltip
    .position(x: frame.midX, y: frame.midY - frame.height - 16)
    }
    }
    }
    }
  3. niaeashes revised this gist Mar 4, 2022. 1 changed file with 49 additions and 19 deletions.
    68 changes: 49 additions & 19 deletions TooltipTest.swift
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,13 @@ struct SizeKey: PreferenceKey {
    }
    }

    struct RectKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
    value = nextValue()
    }
    }

    struct OffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = .zero
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    @@ -18,41 +25,63 @@ struct ContentView: View {

    @State var size: CGSize? = nil
    @State var showOverlay = false
    @State var offset: CGFloat = .zero
    @State var buttonFrame: CGRect = .zero

    @Namespace var scrollId
    @Namespace var contentId

    var body: some View {
    ScrollView {
    ZStack {
    VStack {
    GeometryReader { geometry in
    Button(action: { withAnimation { showOverlay.toggle() } }) {
    Text("Hello, world!")
    .padding()
    .compositingGroup()
    }
    .background(GeometryReader { geometry in
    Color.clear
    .preference(key: SizeKey.self, value: geometry.size)
    .onPreferenceChange(SizeKey.self) { size = $0 }
    })
    ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() }
    Button(action: { withAnimation { showOverlay.toggle() } }) {
    Text("Hello, world!")
    .padding()
    .compositingGroup()
    }
    .frame(width: size?.width, height: size?.height)
    .border(Color.red)
    .overlay(OverlayView().opacity(showOverlay ? 1.0 : 0).offset(x: 0, y: -(size?.height ?? 0) - 16))
    ForEach(0..<30, id: \.self) { i in Text("No.\(i)").padding() }
    .background(GeometryReader { geometry in
    Color.clear
    .preference(key: RectKey.self, value: geometry.frame(in: .named(contentId)))
    .onPreferenceChange(RectKey.self) {
    buttonFrame = $0
    }
    })
    ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() }
    }
    GeometryReader { geometry in
    Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .named(scrollId)).minY)
    }
    .frame(height: 0)
    }
    .modifier(TooltipModifier(frame: buttonFrame) {
    OverlayView()
    })
    .coordinateSpace(name: contentId)
    }
    .coordinateSpace(name: scrollId)
    .onPreferenceChange(OffsetKey.self) {
    offset = $0
    .onPreferenceChange(OffsetKey.self) { _ in
    print(Date(), "Change offset!!!")
    withAnimation { showOverlay = false }
    }
    }
    }

    struct TooltipModifier<Tooltip: View>: ViewModifier {

    let tooltip: Tooltip
    let frame: CGRect

    init(frame: CGRect, @ViewBuilder tooltip: () -> Tooltip) {
    self.frame = frame
    self.tooltip = tooltip()
    }

    func body(content: Content) -> some View {
    ZStack {
    content
    tooltip
    .position(x: frame.midX, y: frame.midY - frame.height - 16)
    }
    }
    }
    @@ -61,9 +90,10 @@ struct OverlayView: View {

    var body: some View {
    Button(action: { print("Tap!!!") }) {
    Text("Tap")
    Text("Can you tap this button?")
    .padding()
    }
    .background(Color(UIColor.systemBackground))
    .clipShape(Capsule())
    .contentShape(Capsule())
    .overlay(Capsule().stroke(Color.accentColor, lineWidth: 2))
  4. niaeashes renamed this gist Mar 4, 2022. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions gistfile1.txt → TooltipTest.swift
    Original file line number Diff line number Diff line change
    @@ -25,9 +25,6 @@ struct ContentView: View {
    var body: some View {
    ScrollView {
    ZStack {
    GeometryReader { geometry in
    Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .named(scrollId)).minY)
    }
    VStack {
    GeometryReader { geometry in
    Button(action: { withAnimation { showOverlay.toggle() } }) {
    @@ -46,12 +43,16 @@ struct ContentView: View {
    .overlay(OverlayView().opacity(showOverlay ? 1.0 : 0).offset(x: 0, y: -(size?.height ?? 0) - 16))
    ForEach(0..<30, id: \.self) { i in Text("No.\(i)").padding() }
    }
    GeometryReader { geometry in
    Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .named(scrollId)).minY)
    }
    .frame(height: 0)
    }
    }
    .coordinateSpace(name: scrollId)
    .onPreferenceChange(OffsetKey.self) {
    offset = $0
    print("Change offset!!!")
    print(Date(), "Change offset!!!")
    }
    }
    }
  5. niaeashes created this gist Mar 4, 2022.
    76 changes: 76 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,76 @@
    import SwiftUI

    struct SizeKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
    value = nextValue()
    }
    }

    struct OffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = .zero
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    value = nextValue()
    }
    }

    struct ContentView: View {

    @State var size: CGSize? = nil
    @State var showOverlay = false
    @State var offset: CGFloat = .zero

    @Namespace var scrollId

    var body: some View {
    ScrollView {
    ZStack {
    GeometryReader { geometry in
    Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .named(scrollId)).minY)
    }
    VStack {
    GeometryReader { geometry in
    Button(action: { withAnimation { showOverlay.toggle() } }) {
    Text("Hello, world!")
    .padding()
    .compositingGroup()
    }
    .background(GeometryReader { geometry in
    Color.clear
    .preference(key: SizeKey.self, value: geometry.size)
    .onPreferenceChange(SizeKey.self) { size = $0 }
    })
    }
    .frame(width: size?.width, height: size?.height)
    .border(Color.red)
    .overlay(OverlayView().opacity(showOverlay ? 1.0 : 0).offset(x: 0, y: -(size?.height ?? 0) - 16))
    ForEach(0..<30, id: \.self) { i in Text("No.\(i)").padding() }
    }
    }
    }
    .coordinateSpace(name: scrollId)
    .onPreferenceChange(OffsetKey.self) {
    offset = $0
    print("Change offset!!!")
    }
    }
    }

    struct OverlayView: View {

    var body: some View {
    Button(action: { print("Tap!!!") }) {
    Text("Tap")
    .padding()
    }
    .clipShape(Capsule())
    .contentShape(Capsule())
    .overlay(Capsule().stroke(Color.accentColor, lineWidth: 2))
    }
    }

    struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
    ContentView()
    }
    }