使用遵循当前SwiftUI语法的@Published属性包装器时,很难定义一个包含带有@Published属性的协议,否则我肯定需要帮助:)
当我在View及其ViewModel之间实现依赖项注入时,我需要定义一个ViewModelProtocol以便注入模拟数据以方便预览。
这是我第一次尝试,
protocol PersonViewModelProtocol {
@Published var person: Person
}
我收到“协议中声明的属性“人”不能具有包装器”。
然后我尝试了这个
protocol PersonViewModelProtocol {
var $person: Published
}
由于保留了“ $”,显然无法正常工作。
我希望能在View和它的ViewModel之间放置一个协议,并利用优雅的@Published语法。非常感谢。
答案 0 :(得分:3)
这是我应该做的事情:
public protocol MyProtocol {
var _person: Published<Person> { get set }
}
class MyClass: MyProtocol, ObservableObject {
@Published var person: Person
public init(person: Published<Person>) {
self._person = person
}
}
尽管编译器似乎有点像(至少是“类型”部分),但是类和协议(https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html)之间的属性访问控制不匹配。我尝试了不同的组合:private
,public
,internal
,fileprivate
。但是没有一个有效。可能是个错误吗?还是缺少功能?
答案 1 :(得分:0)
我们也遇到了这个问题。从Catalina beta7开始,似乎没有任何解决方法,因此我们的解决方案是通过扩展名添加一致性,如下所示:
struct IntView : View {
@Binding var intValue: Int
var body: some View {
Stepper("My Int!", value: $intValue)
}
}
protocol IntBindingContainer {
var intValue$: Binding<Int> { get }
}
extension IntView : IntBindingContainer {
var intValue$: Binding<Int> { $intValue }
}
虽然这是一个额外的仪式,但是我们可以将功能添加到所有IntBindingContainer
实现中,如下所示:
extension IntBindingContainer {
/// Reset the contained integer to zero
func resetToZero() {
intValue$.wrappedValue = 0
}
}
答案 2 :(得分:0)
您必须明确并描述所有综合属性:
protocol WelcomeViewModel {
var person: Person { get }
var personPublished: Published<Person> { get }
var personPublisher: Published<Person>.Publisher { get }
}
class ViewModel: ObservableObject {
@Published var person: Person = Person()
var personPublished: Published<Person> { _person }
var personPublisher: Published<Person>.Publisher { $person }
}
答案 3 :(得分:0)
我的同事提出的解决方法是使用一个声明属性包装器的基类,然后在协议中继承它。仍然需要在您的类中继承它,该类也要符合协议,但是看起来很干净并且可以很好地工作。
# make first pie without labels:
bigger = plt.pie(first_sizes, colors=flatui,
startangle=90, frame=True, radius = 1,
wedgeprops={'edgecolor':'k'})
# feed labels to legend:
plt.gca().legend(first_labels, loc='center right', bbox_to_anchor=(1,0,0.5,0.5))
然后实施:
getCollections()
答案 4 :(得分:0)
我成功地只需要了普通变量,并在实现类中添加了@Published:
Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
/*path-to-project*/packages/app/src/lib/breadcrumbs/__tests__/utils/getBreadcrumbContext.test.js:37
var _ref = import('../../__mocks__'),
^^^^^^
SyntaxError: Unexpected token import
at ScriptTransformer._transformAndBuildScript (../../node_modules/jest-runtime/build/script_transformer.js:403:17)
final class CustomListModel: IsSelectionListModel, ObservableObject {
@Published var list: [IsSelectionListEntry]
init() {
self.list = []
}
...
答案 5 :(得分:0)
我的 MVVM 方法:
// MARK: View
struct ContentView<ViewModel: ContentViewModel>: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text(viewModel.name)
TextField("", text: $viewModel.name)
.border(Color.black)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: ContentViewModelMock())
}
}
// MARK: View model
protocol ContentViewModel: ObservableObject {
var name: String { get set }
}
final class ContentViewModelImpl: ContentViewModel {
@Published var name = ""
}
final class ContentViewModelMock: ContentViewModel {
var name: String = "Test"
}
工作原理:
ViewModel
协议继承了 ObservableObject
,因此 View
将订阅 ViewModel
更改name
具有 getter 和 setter,因此我们可以将其用作 Binding
View
更改 name
属性(通过 TextField)时,会通过 @Published
中的 ViewModel
属性通知 View(并且 UI 已更新)View
可能的缺点:View
必须是通用的。
答案 6 :(得分:-1)
尝试一下
import Combine
import SwiftUI
// MARK: - View Model
final class MyViewModel: ObservableObject {
@Published private(set) var value: Int = 0
func increment() {
value += 1
}
}
extension MyViewModel: MyViewViewModel { }
// MARK: - View
protocol MyViewViewModel: ObservableObject {
var value: Int { get }
func increment()
}
struct MyView<ViewModel: MyViewViewModel>: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text("\(viewModel.value)")
Button("Increment") {
self.viewModel.increment()
}
}
}
}