95c72f1011721b4673e7603a7aa70172
SwiftUI 特色组件之 极简动画提示tool tip(教程含源码)

实战需求

SwiftUI 特色组件之 极简动画提示tool tip

本文价值与收获

看完本文后,您将能够作出下面的界面

截屏2021-07-16 下午11.42.12.png

截屏2021-07-16 下午11.42.12.png

Jietu20210716-234233-HD.gif

Jietu20210716-234233-HD.gif


基础知识

截屏2021-07-16 下午11.43.11.png

截屏2021-07-16 下午11.43.11.png


实战代码

1. 主界面

import SwiftUI


struct ContentView: View {
    var tooltipConfig = DefaultTooltipConfig()

    init() {
        self.tooltipConfig.enableAnimation = true
        self.tooltipConfig.animationOffset = 10
        self.tooltipConfig.animationTime = 1
    }

    var body: some View {
        Text("极简风格工具提示")
            .tooltip(.bottom, config: tooltipConfig) {
                Text("底部抖动提示")
            }
    }
}

2. 核心组件

```
//
// Tooltip.swift
//
// Created by Antoni Silvestrovic on 19/10/2020.
// Copyright © 2020 Quassum Manus. All rights reserved.
//

import SwiftUI

struct TooltipModifier: ViewModifier {
// MARK: - Uninitialised properties

var config: TooltipConfig
var content: TooltipContent

// MARK: - Initialisers

init(config: TooltipConfig, @ViewBuilder content: @escaping () -> TooltipContent) {
    self.config = config
    self.content = content()
}

// MARK: - Local state

@State private var contentWidth: CGFloat = 10
@State private var contentHeight: CGFloat = 10

@State var animationOffset: CGFloat = 0

// MARK: - Computed properties

var arrowRotation: Double { Double(config.side.rawValue) * .pi / 4 }
var actualArrowHeight: CGFloat { config.showArrow ? config.arrowHeight : 0 }

var arrowOffsetX: CGFloat {
    switch config.side {
    case .bottom, .center, .top:
        return 0
    case .leading:
        return (contentWidth / 2 + config.arrowHeight / 2)
    case .leadingTop, .leadingBottom:
        return (contentWidth / 2
            + config.arrowHeight / 2
            - config.borderRadius / 2
            - config.borderWidth / 2)
    case .trailing:
        return -(contentWidth / 2 + config.arrowHeight / 2)
    case .trailingTop, .trailingBottom:
        return -(contentWidth / 2
            + config.arrowHeight / 2
            - config.borderRadius / 2
            - config.borderWidth / 2)
    }
}

var arrowOffsetY: CGFloat {
    switch config.side {
    case .leading, .center, .trailing:
        return 0
    case .top:
        return (contentHeight / 2 + config.arrowHeight / 2)
    case .trailingTop, .leadingTop:
        return (contentHeight / 2
            + config.arrowHeight / 2
            - config.borderRadius / 2
            - config.borderWidth / 2)
    case .bottom:
        return -(contentHeight / 2 + config.arrowHeight / 2)
    case .leadingBottom, .trailingBottom:
        return -(contentHeight / 2
            + config.arrowHeight / 2
            - config.borderRadius / 2
            - config.borderWidth / 2)
    }
}

// MARK: - Helper functions

private func offsetHorizontal(_ g: GeometryProxy) -> CGFloat {
    switch config.side {
    case .leading, .leadingTop, .leadingBottom:
        return -(contentWidth + config.margin + actualArrowHeight + animationOffset)
    case .trailing, .trailingTop, .trailingBottom:
        return g.size.width + config.margin + actualArrowHeight + animationOffset
    case .top, .center, .bottom:
        return (g.size.width - contentWidth) / 2
    }
}

private func offsetVertical(_ g: GeometryProxy) -> CGFloat {
    switch config.side {
    case .top, .trailingTop, .leadingTop:
top Created with Sketch.