SwiftUI 自定义HUD(仿 SVProgressHUD |MBProgressHUD气泡弹窗)

A823ADFF-B866-466F-BEAB-1A95FB83C114.png

SwiftUI上线也有一段时间了,原因还是SwiftUI 支持的iOS系统版本过高(也不稳定很多奇奇怪怪的BUG)导致Store上SwiftUI写得App不是很多,导致关于SwiftUI的第三方工具库少的可怜。
找了半天没找到 SVProgressHUD 这样的第三方库搜索到的几篇制作相同的效果的文章竟然都要收费,对不起我只想白嫖啊
本文介绍自定义HUD气泡弹窗,需要你对SwiftUI有一定的掌握

1 SDProgressHUD

首先我们自定义一SDProgressHUD:的View(先做一个最简单的txt气泡View )

struct SDProgressHUD:View
{
    var  body: some View
    {
        VStack(spacing:10)
        {
              Text("请稍等。。。。。")
        }
        .padding(18)
        .background(Color.black.opacity(0.9),in:RoundedRectangle(cornerRadius: 8))
        .foregroundColor(.white)
        .frame(maxWidth:UIScreen.main.bounds.width-160)
    }
    
}

1.1 SDProgressHUD 的用法

用ZStack将视图布局,做个按钮点击影藏显示

struct ContentView: View {
    @State private var showingHUD = false
    
    var body: some View {
        ZStack(alignment: .top) {
            NavigationView {
                Button("Show/hide HUD") {
                    showingHUD.toggle()
                }
            }
            if showingHUD {
                SDProgressHUD()
                    .zIndex(1)
            }
        }
    }
}
iShot_2022-09-09_15.59.54.gif

这里我们就得到一个简单的气泡弹框,我们需要给它加上动画,和自己倒计时消失机制
1.动画修通过饰符withAnimation
2.在onAppear(显示时)添加定时器 动态修改属性控制显示影藏

struct ContentView: View {
    @State private var showingHUD = false
    
    var body: some View {
        ZStack(alignment: .top) {
            NavigationView {
                Button("Show/hide HUD") {
                    showingHUD.toggle()
                }
            }
            if showingHUD {
                SDProgressHUD()
                    .zIndex(1) 
                     .onAppear {
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                            withAnimation {
                                showingHUD=false
                            }
                        }
                    }
            }
        }
    }
}
iShot_2022-09-09_16.49.03.gif

随着项目的复杂度增加,可能很多地方都需要气泡弹窗,我们不可能每次需要加提示或者loading的时候都在当前页面加上ZStack去修改页面布局,这无疑是过于麻烦不现实的,所以我们在顶层视图上增加 通过注册环境对象“.environmentObject()” 在有需要显示的子视图控制影藏显示即可

2.封装到顶层

2.1创建一个对象控制显示影藏

class ProgressState:ObservableObject
{
    @Published var isPresented: Bool = false
    private(set) var title: String=""
    private(set) var duration:CGFloat=1.5

    func Show(title:String,duration:CGFloat=1.5)
    {

        self.title=title
        self.duration=duration
        withAnimation {
            self.isPresented=true
        }
        
    }
    
    // FIXME:手动隐藏
    func hide()
    {
        withAnimation {
            self.isPresented=false
        }
    }
    
}

2.2为了在各个View中方便调用,继续封装,将SDProgressHUD 作为View的Extension

extension View{
   
   func ProgressHUD(isPresented: Binding) -> some View
   {
       ZStack(alignment:.center)
       {
           self
           
           if isPresented.wrappedValue
           {
               
               Color.black.opacity(0.2)
               SDProgressHUD()
                   .onAppear {
                       DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                           withAnimation {
                               isPresented.wrappedValue=false
                           }
                       }
                   }
                   .zIndex(1)
           }
           
       }
       .ignoresSafeArea()
   }
   
}

2.3在顶层视图添加环境对象

import SwiftUI

@main
struct progressHUDApp: App {
   //add ProgressHUD Model
   @StateObject var progressState:ProgressState=ProgressState()
   var body: some Scene {

       WindowGroup {
           ContentView()
           .ProgressHUD(isPresented: $progressState.isPresented)
            .environmentObject(progressState)
       }
   }
}

2.4在子视图中使用 你只要简单的获取到全局环境变量@EnvironmentObject var proState:ProgressState 修改显示影藏属性即可不用再去关心子视图布局的问题

struct ContentView: View {
   
   @EnvironmentObject var proState:ProgressState
   var body: some View {
       
       
       Button("hide HUD") {
           proState.isPresented=true
       }
       
   }
   
}
iShot_2022-09-09_17.38.20.gif

ok 基础教程结束,我们来仿一个项目中经常用到的SVProgressHUD效果

3.0 我们先看看SVProgressHUD 常用的几种效果

image.png
import Foundation
import SwiftUI

//定义一个枚举控制布局
enum ProgressHUDType{
   case success
   case error
   case loading // FIXME:loading这种方式 需要手动调用 hide 方法
   case txt
   case info
}


//创建一个Progress 状态类
class ProgressState:ObservableObject
{
   @Published var isPresented: Bool = false
   
   private(set) var title: String=""
   private(set) var systemImage: String = ""
   private(set) var duration:CGFloat=1.5
   private(set) var type:ProgressHUDType = .loading
   
   
   
   func Show(type:ProgressHUDType,title:String,duration:CGFloat=1.5)
   {
       
       
       self.type=type
       self.title=title
       self.duration=duration
       withAnimation {
           self.isPresented=true
       }
       
   }
   
   // FIXME:手动隐藏
   func hide()
   {
       withAnimation {
           self.isPresented=false
       }
   }
   
   
   
}



//Progress 视图
struct SDProgressHUD:View
{
   
   @EnvironmentObject var progressState:ProgressState
   
   var  body: some View
   {
       
       VStack(spacing:10)
       {
           if progressState.type == .txt
           {
               Text(progressState.title)
                   
           }else if progressState.type == .error
           {
               
               Image(systemName:"xmark")
                   .resizable()
                   .frame(width: 20, height: 20, alignment:.center)
             
               Text(progressState.title)

           }else if progressState.type == .success
           {
               
               Image(systemName:"checkmark")
                   .resizable()
                   .frame(width: 20, height: 20, alignment:.center)
             
               Text(progressState.title)
               
           }else if progressState.type == .loading
           {
               
               ProgressView()
                      .progressViewStyle(CircularProgressViewStyle(tint: .white))
               Text(progressState.title)
           }
           
           
           
       }
       .padding(18)
       .background(Color.black.opacity(0.9),in:RoundedRectangle(cornerRadius: 8))
       .foregroundColor(.white)
       .frame(maxWidth:UIScreen.main.bounds.width-160)
       .onAppear {
           if progressState.type != .loading
           {
               DispatchQueue.main.asyncAfter(deadline: .now() + progressState.duration) {
                   withAnimation {
                       progressState.isPresented=false
                   }
               }
           }
          
       }


   }
   
}

//封装方便调用
extension View{
   
   func ProgressHUD(isPresented: Binding) -> some View
   {
       ZStack(alignment:.center)
       {
           self
           //为了演示防止点击后抖动的问题,正式项目中不需要"Color.clear"
           Color.clear
           if isPresented.wrappedValue
           {
               
                  //需不需要背景蒙层
//                Color.black.opacity(0.2)
               SDProgressHUD()
                   .zIndex(1)
           }
           
       }
       .ignoresSafeArea()
   }
   
}

我们定义一个ProgressHUDType 枚举来控制不同的样式这里只做了几种我项目中常用的。
当然别忘了 在全局注册环境对象

import SwiftUI

@main
struct progressHUDApp: App {
   //add ProgressHUD Model
   @StateObject var progressState:ProgressState=ProgressState()
   var body: some Scene {

       WindowGroup {
           ContentView()
           .ProgressHUD(isPresented: $progressState.isPresented)
            .environmentObject(progressState)
       }
   }
}

3.1 使用


struct ContentView: View {
   
   @EnvironmentObject var proState:ProgressState
   var body: some View {
       
       VStack(spacing:20)
       {
           Button {
               
               proState.Show(type: .txt, title: "无心爱良夜")
           } label: {
               Text("show Text")
                   .padding()
                   .overlay(RoundedRectangle(cornerRadius: 8, style: .continuous).stroke(.blue, lineWidth: 1))
               
           }
           
           Button {
               
               proState.Show(type: .error, title: "error")
           } label: {
               Text("show error")
                   .padding()
                   .overlay(RoundedRectangle(cornerRadius: 8, style: .continuous).stroke(.blue, lineWidth: 1))
               
           }

           Button {
               
               proState.Show(type: .success, title: "Success")
           } label: {
               Text("show Success")
                   .padding()
                   .overlay(RoundedRectangle(cornerRadius: 8, style: .continuous).stroke(.blue, lineWidth: 1))
               
           }

           HStack{
               Button {
                   
                   proState.Show(type: .loading, title: "loading")
               } label: {
                   Text("show Loading")
                       .padding()
                       .overlay(RoundedRectangle(cornerRadius: 8, style: .continuous).stroke(.red, lineWidth: 1))
                   
               }
               
               Button {
                   
                   proState.hide()
               } label: {
                   Text("hide Loading")
                       .padding()
                       .overlay(RoundedRectangle(cornerRadius: 8, style: .continuous).stroke(.red, lineWidth: 1))
                   
               }
           }
           .foregroundColor(.red)
           .padding(.top,50)
           
       }
       
   }
   
}

loading 类型的需要自己手动影藏,一般用于网络加载开始和结束,你懂得

iShot_2022-09-09_18.44.24.gif

ps,如文章如果有错误的地方请大佬指出

end

ok,以梦为马,不负韶华

你可能感兴趣的:(SwiftUI 自定义HUD(仿 SVProgressHUD |MBProgressHUD气泡弹窗))