Is it possible to change the body variable in the view struct in a function?

Basically, I want to achieve a function that when a button in clicked, embed the current content of the body variable into a HSplitView and set the HSplitView instance to the body variable.

But when I try to do it, it complains the body is a get-only var. Is it possible to make it set-able? Thank you.

Is it possible to make it set-able?

It is not clear enough what you are trying to do, but the answer would be NO.
Generally, you need to construct your body based on some data structure and update the data structure when needed.

when I try to do it

Please show your code, that will clarify what you are trying to do.
Code Block
import SwiftUI
struct ContentView : View {
  var body: some View {
    SingleView()
  }
   
  mutating func splitH(){
    var current_view = body
    body = HSplitView{
      current_view
      current_view
    }
  }
}


Trying to do something like this. I know this won't work. Just wonder what should be the proper way. Basically, programmatically modify the body in functions, not just change a parameter or something.
Accepted Answer

not just change a parameter or something.

You should think different in SwiftUI.

You can write something like this:
Code Block
enum SplittableContent<SingleContent> {
case single(SingleContent)
indirect case split(SplittableContent, SplittableContent)
}
struct SplittableView<SingleContent>: View {
var content: SplittableContent<SingleContent>
var body: some View {
switch content {
case .single(let singleContent):
SingleView(content: singleContent)
case .split(let splittableContentLeft, let splittableContentRight):
HSplitView {
SplittableView(content: splittableContentLeft)
SplittableView(content: splittableContentRight)
}
}
}
}
struct ContentView : View {
@State var splittableContent = SplittableContent.single("SingleView")
var body: some View {
Button("split") {
splittableContent = .split(splittableContent, splittableContent)
}
SplittableView(content: splittableContent)
}
}

(Need Xcode 12.)
Hi OOPer, thank you so much for the demo. Forgive me since I'm still kinda newbee to Swift, especially SwiftUI.
I managed to get the idea of your demo code, but when trying it in xcode, it keeps giving me error in the switch part, saying:

Function declares an opaque return type, but the return statements in its body do not have matching underlying types



t keeps giving me error in the switch part

Are you using Xcode 12 as I wrote?
Oh sorry nope. I'll try again when upgrade.

I'll try again when upgrade.

Thanks for checking. Please remember, to test my code, SingleView needs to be generic.
I tested with:
Code Block
struct SingleView<SingleContent>: View {
var content: SingleContent
var body: some View {
Text(String(describing: content))
}
}

(If I knew what was shown inside SingleView, it could be non-generic.)
Hello @OOPer, the demo code you gave works! Thank you so much!

However, what I'm after is a little bit different. Instead of having a button at the top level and wrapping the root splittableContent into a .split, I actually want to have a split button in each single view (the leaf view). Everytime you click the split, it replace the leaf single view with a .split(single, single). I've tried to achieve it and I got some error at the switch case let part. I've pasted the code below: (I'm still in xcode 11 so I found I have to wrap the return type with AnyView to be able to run)

SingleView.swift
Code Block swift
import SwiftUI
struct SingleView: View {
  @Binding var viewContent: SplittableContent
  var body: some View {
    VStack {
      HStack {
        Button(action: {
          self.viewContent = SplittableContent.Hsplit(self.viewContent, self.viewContent)
        }) {
          Text("|")
        }
        Button(action: {
          self.viewContent = SplittableContent.Vsplit(self.viewContent, self.viewContent)
        }) {
          Text("-")
        }
        Spacer()
      }.padding([.top, .bottom],5)
        .padding([.leading, .trailing], 10)
        .background(Color(red: 0.5, green: 0.5, blue: 0.5))
      Spacer()
      Text("Main window")
      Spacer()
    }
  }
}
struct SingleView_Previews: PreviewProvider {
  static var previews: some View {
    SingleView(viewContent: Binding.constant(SplittableContent.single))
  }
}


ContentView.swift
Code Block swift
import SwiftUI
enum SplittableContent {
  case single
  indirect case Hsplit(SplittableContent, SplittableContent)
  indirect case Vsplit(SplittableContent, SplittableContent)
}
struct SplittableView: View {
  @Binding var content: SplittableContent
  var body: some View {
    switch content {
    case .single:
      return AnyView(SingleView(viewContent: $content))
    case .Hsplit(let splittableContentLeft, let splittableContentRight):
      return AnyView(HSplitView {
        SplittableView(content: splittableContentLeft)
        SplittableView(content: splittableContentRight)
      })
    case .Vsplit(let splittableContentLeft, let splittableContentRight):
      return AnyView(VSplitView {
        SplittableView(content: splittableContentLeft)
        SplittableView(content: splittableContentRight)
      })
    }
  }
}
struct ContentView : View {
  @State var splittableContent = SplittableContent.single
   
  var body: some View {
    VStack{
      SplittableView(content: $splittableContent)
    }
  }
}
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}


what I'm after is a little bit different.

Please include the explicit description about what you really want to do in your opening post.

I got some error at the switch case let part

You should better include the whole error message to reduce reader's time.
As you are not showing whole code (missing HSplitView or VSplitView), it takes more time to test your code.


You make your SingleView as to take a Binding, so you need to pass a Binding to SingleView.init(content:).

To achieve this, you may need to construct Bindings to splittableContentLeft and splittableContentRight.
You can construct sort of computed Bindings in some ways, but as far as I tried, a simple implementation using computed properties did not work as expected when indirect cases are nested.

To make things stable, you can use a reference type instead.

SplittableContent.swift
Code Block
import Combine
enum SplittingState {
case single
case hSplit
case vSplit
}
class SplittableContent: ObservableObject {
@Published var splittingState: SplittingState = .single
@Published var firstContent: SplittableContent?
@Published var secondContent: SplittableContent?
private var firstObserver: AnyCancellable?
private var secondObserver: AnyCancellable?
init() {
firstObserver = $firstContent.sink{_ in self.objectWillChange.send()}
secondObserver = $secondContent.sink{_ in self.objectWillChange.send()}
}
}
extension SplittableContent {
func copy()->SplittableContent {
let newInstance = SplittableContent()
newInstance.splittingState = self.splittingState
newInstance.firstContent = self.firstContent?.copy()
newInstance.secondContent = self.secondContent?.copy()
return newInstance
}
}


SingleView.swift
Code Block
import SwiftUI
struct SingleView: View {
@ObservedObject var viewContent: SplittableContent
var body: some View {
VStack {
HStack {
Button(action: {
self.viewContent.firstContent = self.viewContent.copy()
self.viewContent.secondContent = self.viewContent.copy()
self.viewContent.splittingState = .hSplit
}) {
Text("|")
}
Spacer()
Button(action: {
self.viewContent.firstContent = self.viewContent.copy()
self.viewContent.secondContent = self.viewContent.copy()
self.viewContent.splittingState = .vSplit
}) {
Text("-")
}
}.padding([.top, .bottom],5)
.padding([.leading, .trailing], 10)
.background(Color(red: 0.5, green: 0.5, blue: 0.5))
Spacer()
Text("Main window")
Spacer()
}
}
}
struct SingleView_Previews: PreviewProvider {
static var previews: some View {
SingleView(viewContent: SplittableContent())
}
}


ContentView.swift
Code Block
import SwiftUI
struct SplittableView: View {
@ObservedObject var content: SplittableContent
var body: some View {
switch content.splittingState {
case .single:
return AnyView(SingleView(viewContent: content))
case .hSplit:
return AnyView(HSplitView {
SplittableView(content: self.content.firstContent!)
SplittableView(content: self.content.secondContent!)
})
case .vSplit:
return AnyView(VSplitView {
SplittableView(content: self.content.firstContent!)
SplittableView(content: self.content.secondContent!)
})
}
}
}
struct ContentView : View {
@EnvironmentObject var splittableContent: SplittableContent
var body: some View {
VStack{
Button("show") {
print(self.splittableContent)
}
SplittableView(content: splittableContent)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(SplittableContent())
}
}



@OOPer, thank you so much for your code! It's exactly what I'm going to achieve. I think next step I'll just have to take time digest what you used in your example code, since there's quite a few new things in your code for me lol. But really appreciate your help! It had taken me quite a long time.
Is it possible to change the body variable in the view struct in a function?
 
 
Q