最近使用SwiftUI做项目,鄙人没有想到还会用它做项目,SwiftUI的优势不用多说,把开发过程中遇到问题归纳一下
一 给list加侧滑
在iOS15中 使用 .swipeActions(edge: .trailing,allowsFullSwipe: false) 所有的层叠布局(overlay 和 backgroond 全部失效),。文字颜色,图片大小 都无法修改。通过苹果官方文档只能实现如下:
而如果要实现这种该如何呢???
解决办法: 自定义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 easeOut
i其实系统给我们通过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。