Question regarding code reuse in different views

Hello togehter,

i do have a pretty complex struct view with a couple of properties and methods. Due to the fact, that I want to have a different design of this particular view in dependence of portrait and landscape orientation of a device, I distinguish between a Portrait and Landscape view. Functionality of both subviews should be the same (same properties and same methods), the design and arrangement of single view elements will be different. I don’t want to bother you with the details, therefore I simplified the code snippets to a minimum.

import Foundation
import SwiftUI

struct ScoreboardView: View {

@Environment(\.horizontalSizeClass) var hSizeClass
@Environment(\.verticalSizeClass) var vSizeClass

// Properties
let  minDURATION = 1.0  // global minimum duration time for onLongPressGesture

@State var strStatus: String = "Start Game"
@State var intBalls = 0
@State var intStrikes = 0
@State var intOuts = 0
@State var runsHome = 0
@State var runsGuest = 0
@State var halfInning = inningHalfs.top
@State var intInning = 1
@State var statBase = [false,false,false]

// View
var body: some View {
    
    // device orientation: portrait
    if hSizeClass == .compact && vSizeClass == .regular {
        ScoreboardViewPortrait()
    }
    //device orientation: landscape
    else {
        ScoreboardViewLandscape()
    }
}

// Methods
func resetBaseStatus() {
    for index in 0..<statBase.count {
        statBase[index] = false
    }
}

// MARK: methods - clearBallStrike
func clearBallStrike() {
    intBalls = 0
    intStrikes = 0
}

// MARK: methods - clearBallStrikeOut
func clearBallStrikeOut() {
    intBalls = 0
    intStrikes = 0
    intOuts = 0
    resetBaseStatus()
}

// MARK: methods - changeScoringState
func changeScoringState(selectedElement: selItem, action: String, half: inningHalfs?=nil) {
    ...
}

At the beginning the coding of views ScoreboardViewPortrait and ScoreboardViewLandscape where inside the if brackets. For better code reading I defined own view structs for these two Subviews. Code below is very much simplified too…

struct ScoreboardViewPortrait: View {

    // View - device orientation: portrait
    var body: some View {
        
        VStack {
                    Text(String(intInning))
                        .font(.custom("FaceOffM54", size: 122))
                        .multilineTextAlignment(.center)
                        .onTapGesture {
                            changeScoringState(selectedElement: selItem.inning, action: "inc")
                        }
                        .onLongPressGesture(minimumDuration: minDURATION, maximumDistance: 10) {
                            changeScoringState(selectedElement: selItem.inning, action: "dec")
                        }
            }
    }
}

Now the methods defined in the parent view (ScoreboardView), are not recognized in the child views (ScoreboardviewPortrait and ScoreboardViewLandscape) anymore.

Now my general questions is, how do you write good code for such a case? Any help or links to good articles explaining good code design with examples are appreciated very much!

Instead of having those methods inside the View, you should stick them inside a view model, a class declared as ObservableObject and use that to store the data that drives your Views.

As much as possible, you should be getting the logic out of your Views. You want your Views to be lean and mean. Most, if not all, of those @State properties you have there in ScoreboardView should go in a separate view model and then you reference them in the View. The methods should as well since they modify those properties.

You would then create a view model object with @StateObject in the main ScoreboardView and pass it into the two oriented Views as @ObservedObjects. You then have access to the properties of your view model.

You should keep any properties that are solely related to View-type stuff, like your minDURATION constant, in the View.

The basic outline would be like this:

class Scoreboard: ObservableObject {
    @Published var gameStatus = "Start Game"
    @Published var balls: Int = 0
    @Published var strikes: Int = 0
    @Published var outs: Int = 0
    @Published var inning = 1
    @Published var inningHalf: InningHalf = .top
    //etc...
    
    func changeScoringState(selectedElement: Int, action: String) {
        //...
    }
    
    func clearBallsAndStrikes() {
        //...
    }
    
    //etc...
}

struct ScoreboardView: View {
    
    @Environment(\.horizontalSizeClass) var hSizeClass
    @Environment(\.verticalSizeClass) var vSizeClass
    
    @StateObject var scoreboard = Scoreboard()
    
    // View
    var body: some View {
        
        // device orientation: portrait
        if hSizeClass == .compact && vSizeClass == .regular {
            ScoreboardViewPortrait(scoreboard: scoreboard)
        }
        //device orientation: landscape
        else {
            ScoreboardViewLandscape(scoreboard: scoreboard)
        }
    }
}

struct ScoreboardViewPortrait: View {
    
    // Properties
    let minDURATION = 1.0  // global minimum duration time for onLongPressGesture
    
    @ObservedObject var scoreboard: Scoreboard
    
    // View - device orientation: portrait
    var body: some View {
        VStack {
            Text(String(scoreboard.inning))
                .font(.custom("FaceOffM54", size: 122))
                .multilineTextAlignment(.center)
                .onTapGesture {
                    scoreboard.changeScoringState(selectedElement: selItem.inning, action: "inc")
                }
                .onLongPressGesture(minimumDuration: minDURATION, maximumDistance: 10) {
                    scoreboard.changeScoringState(selectedElement: selItem.inning, action: "dec")
                }
        }
    }
}

struct ScoreboardViewLandscape: View {
    
    // Properties
    let minDURATION = 1.0  // global minimum duration time for onLongPressGesture
    
    @ObservedObject var scoreboard: Scoreboard
    
    // View - device orientation: landscape
    var body: some View {
        //whatever goes here
    }
}

Thanks a lot for your explanations, roosterboy!

Is there any literature available which explains best practice solutions for the MVVM concept?

Thanks, Peter