Binding ObservedObject Init to ContentView - swift

I'm trying to pull more content from server when the user comes to end of the page. I start with loading 5 posts and then 5+5=10 posts. But I cannot bind range so that the view updates with new numbers. Is there a better approach to this or what am I doing wrong here? I tried many things from similiar questions but here is the code:
struct ContentView: View {
#ObservedObject var feedPosts : PostArrayObject
#State var rangeEnd: Int
init(){
let state = State(initialValue: 1)
self._rangeEnd = state
self.feedPosts = PostArrayObject(personalFeed: true, chunkSize: state.projectedValue)
}
var body: some View {
TabView {
NavigationView {
FeedView(posts: feedPosts, title: "Feed", rangeEnd: $rangeEnd)
.toolbar {
....
class PostArrayObject: ObservableObject {
#Published var range: Range<Int> = 0..<1
init(personalFeed: Bool, chunkSize: Binding<Int>) {
DataService.instance.downloadPostsForFollowingInChunks(chunkSize: chunkSize.wrappedValue) { (returnedPosts) in
let shuffledPosts = returnedPosts.shuffled()
self.dataArray.append(contentsOf: shuffledPosts)
self.postsLoaded = true
}
}
struct FeedView: View {
#ObservedObject var posts: PostArrayObject
var title: String
var chunkSize: Int = 5
#Binding var rangeEnd: Int
....
func loadMore(){
rangeEnd = chunkSize
posts.range = 0..<posts.range.upperBound + rangeEnd
}
}

Related

How to bind a #publish datamodel in SwiftUI?

Is there any way to bind a data model in swiftui?
I have coded like below and need to build a struct so that I can use it in multiple views but the problem is to know how to bind a #publish data model in swiftui?
var birds: [PlayerItem] = [PlayerItem(id: UUID(), playershow: false)]
var dogs: [PlayerItem] = [PlayerItem(id: UUID(), playershow: true)]
class Controller: ObservableObject {
#Published var bird = birds
#Published var dog = dogs
}
struct PlayerItem: Hashable {
var id = UUID()
var playerShow: Bool
}
struct ContentView: View {
#EnvironmentObject var control: Controller
var body: some View {
setButton(isOn: $Controller.bird)
}
}
struct setButton: View {
#Binding var isOn: [PlayerItem]
var body: some View {
Button(action: {
self.isOn[0].toggle()
}) {
Text(isOn[0] ? "Off" : "On")
}
}
}
I wrote the following code:
#Binding var isOn: [PlayerItem]
However, it complained the following:
Value of type 'EnvironmentObject<controller>.Wrapper' has no dynamic member 'isOn' using the key path from the root type 'Controller'
try the following code, it shows how to use #Binding and how you have to use playershow
class Controller: ObservableObject {
#Published var bird = [Playeritem(id: UUID(), playershow: false)]
#Published var dog = [Playeritem(id: UUID(), playershow: true)]
}
struct Playeritem: Hashable {
var id = UUID()
var playershow: Bool
}
struct ContentView: View {
#StateObject var control = Controller() // <-- for testing
var body: some View {
setButton(isOn: $control.bird) // <-- here control
}
}
struct setButton: View {
#Binding var isOn: [Playeritem]
var body: some View {
Button(action: {
self.isOn[0].playershow.toggle() // <-- here playershow
}) {
Text(isOn[0].playershow ? "Off" : "On") // <-- here playershow
}
}
}

Making custom get {} set{} to work like dynamic proxy/shortcut to different objects in Array. (SwiftUI) [duplicate]

This question already has answers here:
How to change a value of struct that is in array?
(2 answers)
Closed 1 year ago.
I'm trying to achieve a two way binding-like functionality.
I have a model with an array of identifiable Items, var selectedID holding a UUID of selected Item, and var proxy which has get{} that looks for an Item inside array by UUID and returns it.
While get{} works well, I can't figure out how to make proxy mutable to change values of selected Item by referring to proxy.
I have tried to implement set{} but nothing works.
import SwiftUI
var words = ["Aaaa", "Bbbb", "Cccc"]
struct Item: Identifiable {
var id = UUID()
var word: String
}
class Model: ObservableObject {
#Published var items: [Item] = [Item(word: "One"), Item(word: "Two"), Item(word: "Three")]
#Published var selectedID: UUID?
var proxy: Item? {
set {
// how to set one property of Item?, but not the whole Item here?
}
get {
let index = items.firstIndex(where: { $0.id == selectedID })
return index != nil ? items[index!] : nil
}
}
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
VStack {
// monitoring
MonitorkVue(model: model)
//selections
HStack {
ForEach(model.items.indices, id:\.hashValue) { i in
SelectionVue(item: $model.items[i], model: model)
}
}
}.padding()
}
}
struct MonitorkVue: View {
#ObservedObject var model: Model
var body: some View {
VStack {
Text(model.proxy?.word ?? "no proxy")
// 3rd: cant make item change by referring to proxy
// in order this to work, proxy's set{} need to be implemented somehow..
Button {
model.proxy?.word = words.randomElement()!
} label: {Text("change Proxy")}
}
}
}
struct SelectionVue: View {
#Binding var item: Item
#ObservedObject var model: Model
var body: some View {
VStack {
Text(item.word).padding()
// 1st: making selection
Button {
model.selectedID = item.id } label: {Text("SET")
}.disabled(item.id != model.selectedID ? false : true)
// 2nd: changing item affects proxy,
// this part works ok
Button {
item.word = words.randomElement()!
}label: {Text("change Item")}
}
}
}
Once you SET selection you can randomize Item and proxy will return new values.
But how to make it works the other way around when changing module.proxy.word = "Hello" would affect selected Item?
Does anyone knows how to make this two-way shortct?
Thank You
Here is a correction and some fix:
struct Item: Identifiable {
var id = UUID()
var word: String
}
class Model: ObservableObject {
#Published var items: [Item] = [Item(word: "One"), Item(word: "Two"), Item(word: "Three")]
#Published var selectedID: UUID?
var proxy: Item? {
get {
if let unwrappedIndex: Int = items.firstIndex(where: { value in (selectedID == value.id) }) { return items[unwrappedIndex] }
else { return nil }
}
set(newValue) {
if let unwrappedItem: Item = newValue {
if let unwrappedIndex: Int = items.firstIndex(where: { value in (unwrappedItem.id == value.id) }) {
items[unwrappedIndex] = unwrappedItem
}
}
}
}
}

Binding #Binding / #State to the #Publisher to decouple VM and View Layer

I want to decouple my ViewModel and View layer to increase testability of my Views.
Therefore, I want to keep my property states inside view and only init them as I needed.
But I cannot initialize my #Binding or #States with #Published properties. Is there a way to couple them inside init function?
I just add example code below to
instead of
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#ObservedObject vM = ViewModel()
var body: some View {
Button(action: { vM.int += 1; print(int) }, label: {
Text("Button")
})
}
}
I want to achieve this without using #ObservedObject inside my view.
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#Binding var str: String
#Binding var int: Int
var body: some View {
Button(action: { int += 1; print(int) }, label: {
Text("Button")
})
}
}
extension ContentView {
init(viewModel:ObservedObject<ViewModel> = ObservedObject(wrappedValue: ViewModel())) {
// str: Binding<String> and viewModel.str: Published<String>.publisher
// type so that I cannot bind my bindings to viewModel. I must accomplish
// this by using #ObservedObject but this time my view couples with ViewModel
_str = viewModel.wrappedValue.$str
_int = viewModel.wrappedValue.$int
print("ViewCreated")
}
}
// Testing Init
ContentView(str: Binding<String>, int: Binding<Int>)
// ViewModel Init
ContentView(viewModel: ViewModel)
This way I can't bind them each other, I just want to bind my binding or state properties to published properties.
I have realized that by Binding(get:{}, set{}), I can accomplish that. if anyone want to separate their ViewModel and View layer, they can use this approach:
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#Binding var str: String
#Binding var int: Int
var body: some View {
Button(action: { int += 1; print(int) }, label: {
Text("Button")
})
}
}
extension ContentView {
init(viewModel:ViewModel = ViewModel()) {
_str = Binding ( get: { viewModel.str }, set: { viewModel.str = $0 } )
_int = Binding ( get: { viewModel.int }, set: { viewModel.int = $0 } )
print("ViewCreated")
}
}

Init for a SwiftUI class with a #Binding var

I have a class which I want to initialize with a Binding var that is set in another View.
View ->
struct CoverPageView: View {
#State var numberOfNumbers:Int
var body: some View {
NavigationView {
GeometryReader { geometry in
VStack(alignment: .center, spacing: 0){
TextField("Multiplication Upto:", value: self.$numberOfNumbers, formatter: NumberFormatter())
}
}
}
}
CLASS WHICH NEEDS TO BE INITIALIZED USING THE #Binding var $numberofNumbers -
import SwiftUI
class MultiplicationPractice:ObservableObject {
#Binding var numberOfNumbers:Int
var classNumofNumbers:Int
init() {
self.classNumofNumbers = self.$numberOfNumbers
}
}
The init statement obviously gives the error that self is not initialized and the instance var is being used to initialize which is not allowed.
How do I circumvent this? The class needs to be initialized with the number the user enters on the first view. I have written approx. code here so ignore any typos please.
Typically you'd initialize MultiplicationPractice in CoverPageView with a starting value:
#ObservedObject var someVar = MultiplicationPractice(NoN:123)
And of course, add a supporting init statement:
class MultiplicationPractice:ObservableObject {
init(NoN: Int) {
self.numberOfNumbers = val
}
and you wouldn't want to wrap your var with #Binding, instead wrap it with #Published:
class MultiplicationPractice:ObservableObject {
#Published var numberOfNumbers:Int
...
In your particular case I would even drop the numberOfNumbers var in your CoverPageView, and instead use the direct variable of the above someVar:
struct CoverPageView: View {
//removed #State var numberOfNumbers:Int
#ObservedObject var someVar = MultiplicationPractice(123)
...
TextField("Multiplication Upto:", value: self.$someVar.numberOfNumbers, formatter: NumberFormatter())
You'll notice that I passed in the sub-var of the #ObservedObject as a binding. We can do this with ObservableObjects.
Edit
I see now what you're trying to do, you want to pass a binding along across your ViewModel, and establish an indirect connection between your view and model. While this may not be the way I'd personally do it, I can still provide a working example.
Here is a simple example using your struct names:
struct MultiplicationGame {
#Binding var maxNumber:String
init(maxNumber: Binding<String>) {
self._maxNumber = maxNumber
print(self.maxNumber)
}
}
class MultiplicationPractice:ObservableObject {
var numberOfNumbers: Binding<String>
#Published var MulGame:MultiplicationGame
init(numberOfNumbers: Binding<String> ) {
self.numberOfNumbers = numberOfNumbers
self.MulGame = MultiplicationGame(maxNumber: numberOfNumbers)
}
}
struct ContentView: View {
#State var someText: String
#ObservedObject var mulPractice: MultiplicationPractice
init() {
let state = State(initialValue: "")
self._someText = state
self.mulPractice = MultiplicationPractice(numberOfNumbers: state.projectedValue)
}
var body: some View {
TextField("put your text here", text: $someText)
}
}
Okay, I don't really understand your question so I'm just going to list a few examples and hopefully one of them will be what you're looking for.
struct SuperView: some View {
#State var value: Int = 0
var body: some View {
SubView(value: self.$value)
}
}
struct SubView: View {
#Binding var value: Int
// This is the same as the compiler-generated memberwise initializer
init(value: Binding<Int>) {
self._value = value
}
var body: some View {
Text("\(value)")
}
}
If I misunderstood and you're just trying to get the current value, do this
struct SuperView: some View {
#State var value: Int = 0
var body: some View {
SubView(value: self.value)
}
}
struct SubView: View {
let value: Int
// This is the same as the compiler-generated memberwise initializer
init(value: Int) {
self.value = value
}
var body: some View {
Text("\(value)")
}
}

swiftui #State value depends on #ObservedObject ViewModel init error

I have simple viewModel:
final class EmployeeListViewModel: ObservableObject {
#Published var list = [Employee]()
init() {
// some request
self.list = [Employee, Employee]
}
}
And have a view:
struct EmployeeView: View {
#ObservedObject var viewModel = EmployeeListViewModel()
#State private var showContents: [Bool] = Array(repeating: false, count: viewModel.list.count)// <- error throws here
var body: some View {
GeometryReader { fullView in
ScrollView {
VStack(spacing: 40) {
ForEach(self.viewModel.list) { employee in
Text(employee.firstName).foregroundColor(.black)
}
}
}
}
}
}
Error text:
Cannot use instance member 'viewModel' within property initializer; property initializers run before 'self' is available
I tried change it with init:
struct EmployeeView: View {
#ObservedObject var viewModel = EmployeeListViewModel()
#State private var showContents: [Bool]
init() {
_showContents = State(initialValue: Array(repeating: false, count: viewModel.list.count)) // <- error
}
var body: some View {
GeometryReader { fullView in
ScrollView {
VStack(spacing: 40) {
ForEach(self.viewModel.list) { employee in
Text(employee.firstName).foregroundColor(.black)
}
}
}
}
}
}
But it also throws error:
'self' used before all stored properties are initialized
this throws on I call viewModel on init()
How to solve it? #State i use for card view. There I simplified views for easy understand.
First initialise the state variable to an empty array
#State private var showContents: [Bool] = []
then set it in the init
init() {
showContents = Array(repeating: false, count: viewModel.list.count)
}
You shouldn't initialise the view model property in the view but rather use dependency injection
init(viewModel: EmployeeListViewModel) {
self.viewModel = viewModel
showContents = Array(repeating: false, count: viewModel.list.count)
}
Here is possible solution
struct EmployeeView: View {
#ObservedObject var viewModel: EmployeeListViewModel // << declare
#State private var showContents: [Bool] // << declare
init() {
let vm = EmployeeListViewModel() // create here !!
// initialize both below
self.viewModel = vm
self._showContents = State(initialValue: Array(repeating: false,
count: vm.list.count))
}