SwiftUI项目总结

最近使用SwiftUI做项目,鄙人没有想到还会用它做项目,SwiftUI的优势不用多说,把开发过程中遇到问题归纳一下

一 给list加侧滑

在iOS15中 使用 .swipeActions(edge: .trailing,allowsFullSwipe: false) 所有的层叠布局(overlay 和 backgroond 全部失效),。文字颜色,图片大小 都无法修改。通过苹果官方文档只能实现如下:

image.png

而如果要实现这种该如何呢???

截屏2024-12-04 15.51.34.png

解决办法: 自定义Drap手势,利用ViewModifier进行实现。

ViewModifier是SwiftUI中一个用于封装和重用视图修改逻辑的协议。通过实现这个协议,可以创建自定义的修改器(modifier),这样就可以应用于任何视图,以改变其外观、行为或其它属性。使用ViewModifier可以方便地统一调整全局效果,提高代码的可重用性和维护性。

二 给某个组件加部分圆角的方法

2.1 使用clipShape
 Blur()
            .clipShape(CustomRoundedRectangle(corners: [.bottomLeft,.bottomRight], radius: 24~))
2.2 使用 background
 .background {
                CustomRoundedRectangle(corners: [.bottomLeft,.bottomRight], radius: 24~)
            }

使用background在某些视图上设置圆角没效果,比如这里的 Blur()组件

struct Blur: UIViewRepresentable {
    let style: UIBlurEffect.Style = .dark
    func makeUIView(context: Context) -> UIVisualEffectView {
        return UIVisualEffectView(effect: UIBlurEffect(style: style))
    }

    func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
        uiView.effect = UIBlurEffect(style: style)
    }
}
struct CustomRoundedRectangle: Shape {
    var corners: UIRectCorner
    var radius: CGFloat
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

三 Image的使用误区

Image(“face_1”) face_1必须在Assets.xcassets的图片
如果不在Assets.xcasse里面可以使用 Image(uiImage: UIImage(named: taskItem.taskEmoIcon ?? "") ?? UIImage())
Image(<#T##String#>, bundle: <#T##Bundle?#>) 它可以吗 bundle 又该如何设置呢???

四 Accessing StateObject's object without being installed on a View. This will create a new instance each time

在init里面更新StateObject的值,不行的 。
https://stackoverflow.com/questions/68930434/accessing-stateobjects-object-without-being-installed-on-a-view-this-will-crea

错误代码:

  @StateObject var  tabBarViewModel :DailDeilTabBarViewModel =  DailDeilTabBarViewModel()
init() {
         DaiDelPushManger.manger.didReceivePushMsg = { targetContentIdentifier in
            // 跳转到首页
            if tabBarViewModel.tabBarIndex != 0 {
                tabBarViewModel.tabBarIndex = 0
            }
        }
    }

五 Swiftui 处理冷启动

struct DailyDelightApp: App 里面init方法并不能处理冷启动业务。
iOS使用 @UIApplicationDelegateAdaptor 装饰器,来处理冷启动的相关业务。
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate


struct DailyDelightApp: App {
    let persistenceController = PersistenceController.shared
    @StateObject var  tabBarViewModel :DailDeilTabBarViewModel =  DailDeilTabBarViewModel()
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    init() {
        //appInit()
    }
}
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 在这里处理冷启动逻辑
        DebugPrint("应用冷启动完成")
        return true
    }
}

6 Swiftui 类中嵌套enum和Struct

SwiftUI 类中确实可以嵌套枚举。这种做法可以在类内部定义特定的枚举类型,以便专门用于该类。这有助于组织相关的功能和数据,使得代码更加模块化和易于维护。
万万没想到,太high了!!!!!

class SubScribleViewModel:ObservableObject {
    enum SubScribleType {
    case Month
    case FreeYear
    }
}

7 Animation

7.1 timingCurve
在 SwiftUI 中,animation.timingCurve 是一种用于定义动画时序的方式,它通过贝塞尔曲线控制动画的速度变化。这种方法允许开发者创建自定义的动画效果,使得动画过程更加生动和自然。
timingCurve 是一种插值动画,可以定义动画在不同时间点的速度变化。具体来说,它包含四个参数,这些参数表示贝塞尔曲线控制点的位置:

animation(timingCurve: c1x, c1y, c2x, c2y)  
c1x 和 c1y: 第一个控制点的x和y坐标。
c2x 和 c2y: 第二个控制点的x和y坐标。
Each(0..<5) { index in
                Group {
                    Circle()
                        .frame(width: geometry.size.width / 5, height: geometry.size.height / 5)
                        .scaleEffect(calcScale(index: index))
                        .offset(y: calcYOffset(geometry))
                }.frame(width: geometry.size.width, height: geometry.size.height)
                    .rotationEffect(!self.isAnimating ? .degrees(0) : .degrees(360))
                    .animation(Animation
                                .timingCurve(0.5, 0.15 + Double(index) / 5, 0.25, 1, duration: 1.5)
                                .repeatForever(autoreverses: false))

常见的easeInOut easeIn easeOuti其实系统给我们通过timingCurve封装的特殊类型,我们只是拿过来用而已。

8 SwiftUI 可以在主线程刷新UI,而不闪退

我们知道在UIKit中在子线程刷新UI,闪退是必然的。而SwiftUI 子线程刷新 UI 时不会闪退,因为 SwiftUI 中的视图能够直接在后台线程中更新。但是,必须要保证 UI 的更新是在 UI 线程(也称为主线程)中进行的,这是由于 UIKit 和 SwiftUI 都是设计为在主线程上操作 UI 元素,以保证响应性和流畅度。如果在子线程中直接进行 UI 更新,应用程序可能会出现闪退或其他未知行为。
这点确实出乎意料啊!!!!

 subScribleViewModel.goSubscriblePay{ message, succeed in
            DispatchQueue.main.async {
                subScribleViewModel.isShowLoading = false
                showToast(msg: message)
                if succeed {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
                        isShowSubScribleAlert = false
                    })
                }
            }
        }

9 GeometryReader

过度依赖 GeometryReader 会导致视图布局变得僵化,失去了 SwiftUI 的灵活性优势。
GeometryReader 打破了 SwiftUI 声明式编程的理念,使得需要直接操作视图框架,更接近命令式编程。少用!!!!

  // SubscribeNow
           // GeometryReader {  render in
                Text("SubscribeNow".getInternationalText(key: .SubscribeNowText))
                    .font(.system(size: 16~,weight: .medium))
                    .foregroundStyle(Color(hex: "#FFFFFFFF") ?? .black)
                    .padding(.vertical,18~)
                    .padding(.horizontal,116~)
                    .modifier(DailDelTypeCornerModifier(cornerRadius: 28~, fillColor: "#FF0A0F14"))
                    .padding(.top,subScribleViewModel.subScribleType == .Month ? 32~ : 16~)
                    .onTapGesture {
                        goSubsciblePay()
                }
            //}

用上后,布局就会混乱。

9 监听app 进入前后和后台

.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
                   DebugPrint("进入后台!")
            }
            .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
                DebugPrint("进入前台!")
                isOpenPush(isOnReminder: true)
                // 第二次如果没授权 就不要弹窗了
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
                    isOpenPushPresented = false
                })
            } 

10 键盘遮挡输入框的解决防范

键盘遮挡是个非常头疼的问题,无论是uikit还是swifui,在swifui可以通过onReceive来监听键盘活动:

 .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
                          
                           guard let userInfo = notification.userInfo,
                                 let keyboardEndFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
                               return
                           }
                           let keyboardHeight = keyboardEndFrame.height
                           bottomPadding = keyboardHeight
                       }
                       .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
                               bottomPadding = 0
                       }

10 error: Couldn't lookup symbols:

DailyDelight.DailDelHomePage.isCurrentDayTask.getter : Swift.Bool
Xcode 无法po SwiftUI 的装饰器的变量
po self._someProperty而不是po self.someProperty。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容