Swift-ui 3

List: Apple提供的一个默认实现,类似于VStack,但不一样。我们估计List跟UITableView一致,比如动态加载和render(就是cell复用),而VStack可能没有使用这种优化。You can use ForEach views inside List views to have both dynamic and static content – a very powerful feature of SwiftUI. 一般我们都是采用这个方法来实现动态内容。List有一个好处,就是里面的内容动态变化的时候,其高度也能变化。但List存在一些问题,比如分割线(非常讨厌)和背景色。如果使用ScrollView + VStack 来代替List,目前发现无法支持动态调整大小。而ScrollView + LazyVStack目前的测试情况看还不错,但不确定是否Cell重用。
iOS13:
.onAppear(){
if #available(iOS 14.0, *) {
// iOS 14 doesn't have extra separators below the list by default.
} else {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
}

// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
还有一种方法:iOS14
List {
ForEach(0..<3) { _ in
VStack {
Text("Hello, World!").padding(.leading)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets())
.background(UIColor.systemBackground)
}
}
给出建议:iOS14的话,不要使用List
这个是设置Text显示行数,而且是可动态调整的。
如果VStack里面包含一个List+其他,则可能存在一个spcing=8,这个时候可以通过一些方法去掉这个spcing: .padding(.top, -8)
或者设置offset
struct XButton<Content>: View where Content: View {
let content: () -> Content
let action: () -> Void
init(@ViewBuilder content: @escaping () -> Content, action: @escaping () -> Void) {
self.content = content
self.action = action
}
也可以无需:@ViewBuilder这部分,也就是UI无需外部传入
struct PrimaryLabel: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color.red)
.foregroundColor(Color.white)
.font(.largeTitle)
}
}
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI")
.modifier(PrimaryLabel())
}
}
一个简单的应用场景是给一些control设置default值。ViewModifier其实是create一个新的View进行下一步,一般而言都是let/val而不是变量;但这个create可能是copy,因此效率并不会损失多少,但是安全性提高了不少。
默认的spacing是8,一般需要设置为0
ScrollView目前还不知道如何刷新动态内容,也就是动态内容下怎么调整高度。
而List本身就可以(参考TableView的实现)
.background(Color.white.shadow(color: Color.shadow, radius: 4, x: 0, y: 0)
采用一个View来遮挡+zIndex
VStack {
Rectangle().frame(height: 4*2).foregroundColor(Color.white).zIndex(10)
ThisView().background(
Color.white.shadow(color: Color.shadow, radius: 4, x: 0, y: 0)
).zIndex(9)
}
- 在自然zOrder中,先声明的在index小。上面是有矩形来挡住Top的阴影
- 需要2倍的radius才能挡住
- 如果是挡住下面的阴影,则后者zIndex就比较大而无需调整了
一般情况下,List内部的Button是无法点击的,而是通过
.onTapGesture{},Text/Button均可以,但Button没有下按的效果。
可以通过 .buttonStyle(BorderlessButtonStyle()) 来设置按下的效果
如果在NavigationView下的NavigationLink则是另外一回事。NavigationLink在List里面同样也不能直接使用actionScrollView里面的Button没有这个问题。
//
var cancellable = AnyCancellable?
//
cancellable = handler.$info.sink{[weak self] (info: XInfo) in
// 线程安全
}
//
deinit {
cancellable?.cancell()
}
- 对combine不熟,还不是很明白sink
- cancellable不能是临时变量,否则会自动销毁
在有NaviationBar的地方,我们只需要设置VC的title即可显示在nav上,但有时却不一定。需要
self.navigationController?.navigationBar.topItem?.title = ""
一般而言,swiftui和外面共享的数据可以使用EnvironmentObject,而且在View的子View也可直接获取,因此比较方便,但注意不要乱用,否则满天飞。
View的非子View就无法获取,如presenter,需要单独处理。
给View添加一个构造函数,用于传入参数,如
public struct XView: View {
private let param1: Param
public init(param1: Param){ }}
class XViewModel: ObservableObject {
var label: String
@Published var title: String
@Published var name: String?
}
如果XViewModel和一个View绑定,那么当title/name发生变化的时候,View会自动刷新——也就是重新生成body,包括name从nil到有值。label发生变化的时候则不会trigger
如果在View之外,比如常规code里面,
.onReceive(xViewModel.objectWillChange){
// xViewModel
}
- XViewModel必须是class而不是struct,这个好理解,毕竟class是引用,而struct是值而不是对象
- onReceive(xViewModel.objectWillChange) 这个可能会被触发多次,比如title/name各触发一次
- label的值虽然不触发,但如果主动去拿,也总是新的,这个是当然的
如果Published是一个数组:
则这个数组Add/remove会触发,但某个元素的值被修改是不会触发的。因此想要监视某个元素的属性修改,则不得不再来一层。
无论是在Test还是pod包中, 不能直接使用Bundle.main
let bundle = Bundle(for: Self.self)
let url = bundle.url(forResource: "file", withExtension: "json")
前面说过,Self.self: Self是这个类的简写,可以用类代替,整个的意思是:当前类的实例
# decode from response
let model = JSONDecoder().decode(XNetworkModel.self, from: response.data)
# Data -> String
let str = String(decoding: data, as: UTF8.self)
# dic -> Data
var dic = [String: Any]
let data = JSONSerialization.data(withJSONObject: dic, options: [.prettyPrinted])
# String -> Data
let data = Data(str.utf8)
# 一般不使用 NSDictionary而是直接Dictionary
let dic = [String: Any]
struct Passthrough<Content>: View where Content: View {
let content: () -> Content

init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}

var body: some View {
content()
}
}
这样我们就可以包装标准控件,还可以在后面添加事件,和content完全一样。
Button(action: {
print("sign up bin tapped")
}) {
Text("SIGN UP")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 18))
.padding()
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(Color.white, lineWidth: 2)
)
}
- 由于Button通常是一个Text加上action,因此我们只需要控制Text成圆角即可
- 圆角使用RoundedRectanglei实现,他有前景色,而没有背景色
- VStack 或者 overlay实现
- overlay就是前景view不能超出背景view,因此可以实现一些clip
ZStack {
RoundedRectangle(cornerRadius: 20).foregroundColor(Color.green)
Text("Done").foregroundColor(Color.white)
}
或者
RoundedRectangle(cornerRadius: 20).foregroundColor(Color.green).overlay(
Text("Done").foregroundColor(Color.white)
)

https://www.codegrepper.com/code-examples/swift
Saturday, Nov 21, 2020 > EEEE, MMM d, yyyy
11/21/2020 > MM/dd/yyyy
11-21-2020 13:04 > MM-dd-yyyy HH:mm
Nov 21, 1:04 PM > MMM d, h:mm a
November 2020 > MMMM yyyy
Nov 21, 2020 > MMM d, yyyy
Sat, 21 Nov 2020 13:04:30 +0000 > E, d MMM yyyy HH:mm:ss Z
2020-11-21T13:04:30+0000 > yyyy-MM-dd'T'HH:mm:ssZ
21.11.20 > dd.MM.yy
13:04:30.890 > HH:mm:ss.SSS
M: 数字月,如 11, 1
MM: 数字月 如 11, 01
MMM: 月的英文缩写 如 Nov
MMMM: 月的英文全写 如 November
EEEE: 星期的全写 如 Saturday
a: AM/PM
hh: 指12小时表示法
HH: 指24小时表示法
ss: 秒
SSS: 毫秒
cardViewModels.append<CardViewModel>(contentsOf: [basic, chart, example])在这句中,由于basic/chart/example并不是同一个类型,而是基于同一个protocol,则这句话无法通过编译,而只能一个一个的append。如果基于同一个基类的,应该是可以的。cardViewModels.append(basic)
protocol CardViewModel: class {
var headline: String? { get }
var type: CardType { get }
}
也就是这个protocol只能被class继承而不能是struct/enum
protocol ObservableObject : AnyObjectAnyObject: The protocol to which all classes implicitly conform.
AnyClass: The protocol to which all class types implicitly conform.
----
AnyObject指所有的类,但Any可以指任意的类和结构。
AnyObject其实上例中的 class一样的功能。
----
这是apple提供的例子,也就是说如果我们要使用ObservableObject则没有办法我们是能使用class而不是struct。这还是可以理解的,因为class是引用,而struct只能安全copy。
struct LandmarkList: View {
@EnvironmentObject private var userData: UserData
}
final class UserData: ObservableObject {
@Published var showFavoritesOnly = false
@Published var landmarks = landmarkData
@Published var hikes = hikeData
}
到底要声明成怎样才合适?
- Presenter不是外部变量而是内部,因此无需使用@EnvironmentObject
- Presenter是否需要 @ObservedObject? 如果是,则它是一个class而不是struct
- Presenter与View是共生关系,或者说Presenter是依赖于View
Apple提供的这个代码中,其中在数据的共享上有一个问题。当从List到Detail页面的时候,它把整个UserData当作EnvironmentObject传了进去,同时还传了一个Landmark. 本身这样不是很合理,对于detail页面,他无需知道其他对象。final class UserData: ObservableObject {
@Published var showFavoritesOnly = false
@Published var landmarks = landmarkData
}
这是说UserData以及属性landmarks是可observable,但并不意味每个landmark是Observable的。
但是如果不采用Apple的那种方式,就会遇到麻烦。
- 如果只是静态数据,怎么都好说,用一个struct当作var传入即可
-
A property wrapper type for an observable object supplied by a parent or ancestor view.@frozen @propertyWrapper struct EnvironmentObject<ObjectType> where ObjectType : ObservableObjectEnvironmentObject
- 是一个 ObservableObject
- 变量是被parent或者前一个view持有
- 属于swiftui而不是 combine
- 通过environmentObject() 设置
- 其必须是一个class而不是struct,也就是引用不是copy
- 这种也可以说inject注入的方式
An environment object invalidates the current view whenever the observable object changes. If you declare a property as an environment object, be sure to set a corresponding model object on an ancestor view by calling its environmentObject(_:) modifier.EnvironmentObject无需处处传入
看到了这个例子以及这个例子,然后试了一下,也就是说只需要在rootView那儿声明一下然后传入即可,之后的view只需要声明这个变量而无需显示的传入。这样的话
- 整个View及其子View都可以使用,也就是只需要声明一下即可使用
- 在使用navigation的时候,后一个view也不需要显示传入
- 当然显示传入本身也没有问题(没准兼容性更好)
也就说一旦定义了一个EnvironmentObject则整个内部的view和后续的view都可以直接使用。这也有点太可怕了。
@Published A type that publishes a property marked with an attribute.
- 发布的属性必须是一个class
- 可以与 ObservableObject结合使用,也可以单独使用
- 属于combine
如果我声明在presenter里面,则无法工作。在View里面正确的方式:
@FetchRequest(entity: Event.entity(), sortDescriptors: [],
predicate: NSPredicate(format: "time > %@", Calendar.current.startOfDay(for: Date()) as NSDate)
) var fetchEvent: FetchedResults<Event>
这种方式能工作,但不是最佳方式。需要fetchCategory.wrappedValues
let fetchCategory = FetchRequest<Category>(entity: Category.entity(), sortDescriptors: [NSSortDescriptor(key:"cid", ascending:true)])
如果在presenter里面,则需要采用
let fetchCategory: NSFetchRequest<Category> = Category.fetchRequest()
然后
context.fetch(fetchEvent).forEach{}
但是要记住:context无法通过Environment传递到Presenter,只能通过函数参数传递。

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store