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<Bool>) -> 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<Bool>) -> 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,以梦为马,不负韶华

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,585评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,923评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,314评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,346评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,718评论 3 291
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,828评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,020评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,758评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,486评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,722评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,196评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,546评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,211评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,132评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,916评论 0 200
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,904评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,758评论 2 274

推荐阅读更多精彩内容