swift ForEach(0..〈不从0开始

ndasle7k  于 2023-03-28  发布在  Swift
关注(0)|答案(1)|浏览(124)

下面的代码:

VStack (spacing: 0) {
                 ForEach (0..<myMaze.height, id: \.self) { (y: Int) in
                    HStack (spacing: 0) {
                        ForEach (0..<myMaze.width, id: \.self) { (x: Int) in
                            DrawRoom(xPos: x, yPos: y)
                        } // ForEach x
                    } // HSTack
                } // ForEach y
            } // VSTack

struct DrawRoom: View {
    @ObservedObject var myMaze = Maze.sharedInstance
    var xPos: Int
    var yPos: Int
    var body: some View {
        Square(value: myMaze.value)
            .stroke(Color.gray, style: StrokeStyle(lineWidth: 3))
            .frame(width: myMaze.sideLength, height: myMaze.sideLength)
    }
    init(xPos: Int, yPos: Int) {
        self.xPos = xPos
        self.yPos = yPos
        myMaze.value = myMaze.mazeData[xPos][yPos]
        print("DrawRoom: [\(xPos), \(yPos)]")
    }
}

...在控制台中显示以下输出:

X, Y
DrawRoom: [0, 2]
DrawRoom: [1, 2]
DrawRoom: [2, 2]
DrawRoom: [0, 0]
DrawRoom: [1, 0]
DrawRoom: [2, 0]
DrawRoom: [0, 1]
DrawRoom: [1, 1]
DrawRoom: [2, 1]

令人惊讶的是,“Y”从2开始,然后是0,最后是1,但它应该如下所示:

X, Y
DrawRoom: [0, 0]
DrawRoom: [1, 0]
DrawRoom: [2, 0]
DrawRoom: [0, 1]
DrawRoom: [1, 1]
DrawRoom: [2, 1]
DrawRoom: [0, 2]
DrawRoom: [1, 2]
DrawRoom: [2, 2]

有人能告诉我为什么吗?我的代码有问题吗?
作为参考,“Square”根据myMaze的值绘制线条。mazeData[xPos][yPos]
下面是整个代码:

//
//  Maze.swift
//  NewMaze
//
//  Created by Philippe Lagarrigue on 26/12/2020.
//

import Foundation
import SwiftUI

let side = ["North", "East", "South", "West"]
let direction = [1, 2, 4, 8]
let wall = (North: 1, East: 2, South: 4, West: 8) // 15 means all walls
let exit = (toNorth: 16, toEast: 32, toSouth: 64, toWest: 128) // 0 means no exit

struct Room {
    var x: Int
    var y: Int
    var roomsToExit: Int
}

class Maze: ObservableObject {
    // Properties
    static let sharedInstance = Maze()
    var viewWidth = CGFloat(0.0) //{ didSet { update(flag: 2) } }
    var viewHeight = CGFloat(0.0) //{ didSet { update(flag: 2) } }
    var ready2Draw = false
    var width = 0 // Number of rooms (horizontally)
    var height = 0 // Number of rooms (vertically)
    @Published var widthDouble = CGFloat(3.0) { didSet { ready2Draw = false } }
    @Published var heightDouble = CGFloat(3.0) { didSet { ready2Draw = false } }
    var oneWay = true
    var exitRoom = Room(x: 0, y: 0, roomsToExit: 0)
    var farestRoom = Room(x: 0, y: 0, roomsToExit: 0)
    var currentPos = Room(x: 0, y: 0, roomsToExit: 0)
    var mazeData = [[Int]]()
    //var width: Int { return Int(widthDouble) } // Number of rooms (horizontally)
    //var height: Int { return Int(heightDouble) } // Number of rooms (vertically)
    var numberOfRooms: Int { return self.width * self.height }
    var sideLength = CGFloat(40.0) //{ didSet { print("sideLength: \(sideLength)") } }
    var value = 15 // { didSet { print("value: \(value)") } }
    
    func defineExit() {
        let choosenSide = Int.random(in: 0...3) // N, E, S or W
        // Fill array with 15 which means that all walls are set
        mazeData = Array(repeating: Array(repeating: 15, count: self.height), count: self.width)
        //var count = 0
        //for x in 0..<width {
        //    for y in 0..<height {
        //        print(x, y, count, count/height, count%height)
        //        mazeData[x][y] = 15
        //        count += 1
        //    }
        //}
        
        
        print("\nchoosenSide: \(side[choosenSide]) (\(choosenSide))")
        if (choosenSide + 1) % 2 == 0 {
            print("Axe: E-W")
            exitRoom.x = choosenSide == 3 ? 0 : self.width - 1
            exitRoom.y = Int.random(in: 1..<self.height)
        } else {
            print("Axe: N-S")
            exitRoom.x = Int.random(in: 1..<self.width)
            exitRoom.y = choosenSide == 2 ? self.height - 1 : 0
        }
        currentPos.x = exitRoom.x
        currentPos.y = exitRoom.y
        currentPos.roomsToExit = 1
        //print("The exit room is at \(exitRoom.x), \(exitRoom.y)")
        mazeData[currentPos.x][currentPos.y] = 15 - direction[choosenSide]
        print("The exit room is at [\(exitRoom.x), \(exitRoom.y)] => \(mazeData[currentPos.x][currentPos.y])")
    }
    
    init() {
        //buildMaze()
    }
    
    func update(flag: Int) {
        if flag & 1 == 1 {
            width = Int(widthDouble)
            height = Int(heightDouble)
        }
        if flag & 2 == 2 {
            let viewW = viewWidth/widthDouble
            let viewH = viewHeight/heightDouble
            let side = (viewW < viewH ? viewW : viewH) - 1.0
            self.sideLength = side - side * 0.1
            print("Maze init:", width, "x", height, "=", numberOfRooms)
        }
        ready2Draw = false
    }
    
    func dummy() {
        let wd = widthDouble
        let hd = heightDouble
        widthDouble = 3
        heightDouble = 3
        update(flag: 3)
        defineExit()
        widthDouble = wd
        heightDouble = hd
    }
    
    func buildMaze(){
        dummy() // This is the only way I found to get the maze displayed correctly
        update(flag: 3)
        defineExit()
        //var rooms = numberOfRooms
        //while (rooms > 0) {
        //    newRoom()
        //    rooms -= 1
        //}
        ready2Draw = true
    }

    func newRoom() {
        
    }
}
//
//  ContentView.swift
//  NewMaze
//
//  Created by Philippe Lagarrigue on 26/12/2020.
//

import SwiftUI
import Combine   // << needed for Just publisher below

struct ContentView: View {
    @ObservedObject var myMaze: Maze = .init()
    @State private var selectedView = 0
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    var body: some View {
        TabView(selection: $selectedView) {            
            NavigationView() .tabItem { Image("navigation"); Text("Navigation") }.tag(0)
                .onReceive(timer) { input in selectedView = 1; self.timer.upstream.connect().cancel() }
            SettingsView() .tabItem { Image("settings"); Text("Settings") }.tag(1)
            MapView() .tabItem { Image("map"); Text("Map") }.tag(2) 
        }
        //.onReceive(Just(selectedView)) { print($0) }
        //.onLongPressGesture { myMaze.ready2Draw = true }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct NavigationView : View {
    @ObservedObject var myMaze = Maze.sharedInstance
    var body : some View {
        GeometryReader { geometry in
            Path { path in
                myMaze.viewWidth = geometry.size.width
                myMaze.viewHeight = geometry.size.width
            }
        }
    }
}

struct Square: Shape {
    //@ObservedObject var myMaze = Maze.sharedInstance
    var value: Int
    func path(in rect: CGRect) -> Path {
        var path = Path()
        if (value & wall.North == wall.North) {
            path.move(to: CGPoint(x: rect.minX, y: rect.minY))
            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        }
        if (value & wall.East == wall.East) {
            path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        }
        if (value & wall.South == wall.South) {
            path.move(to: CGPoint(x: rect.maxX, y: rect.maxY))
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
        }
        if (value & wall.West == wall.West) {
            path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
            path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
        }
        return path
    }
    init(value: Int) {
        self.value = value
        //if value != 15 {
        //    print("Square: \(value)")
        //}
    }
}

struct DrawRoom: View {
    @ObservedObject var myMaze = Maze.sharedInstance
    var xPos: Int
    var yPos: Int
    let numbers = false
    var body: some View {
        if numbers {
            Text("\(myMaze.value, specifier: "%.02d")")
                .font(.system(size: 18, design: .monospaced))
                .foregroundColor(myMaze.value == 15 ? Color.black : Color.red)
        } else {
            Square(value: myMaze.value)
                .stroke(Color.gray, style: StrokeStyle(lineWidth: 3))
                .frame(width: myMaze.sideLength, height: myMaze.sideLength)
        }
    }
    init(xPos: Int, yPos: Int) {
        self.xPos = xPos
        self.yPos = yPos
        myMaze.value = myMaze.mazeData[xPos][yPos]
        print("DrawRoom: [\(xPos), \(yPos)]")
    }
}

struct MapView : View {
    @ObservedObject var myMaze = Maze.sharedInstance
    var body : some View {
        if myMaze.ready2Draw {
            VStack (spacing: 0) {
                 ForEach (0..<myMaze.height, id: \.self) { (y: Int) in
                    HStack (spacing: 0) {
                        ForEach (0..<myMaze.width, id: \.self) { (x: Int) in
                            DrawRoom(xPos: x, yPos: y)
                        } // ForEach x
                    } // HSTack
                } // ForEach y
            } // VSTack
        } else {
            Button(action: { action() }) { Text("Build maze") }
                .padding()
                .background(Color.red)
                .cornerRadius(20)
                .foregroundColor(.white)
                .font(.title)
                .shadow(color: .gray, radius: 20.0, x: 10, y: 10)
        }
    } // body
    func action() {
        myMaze.buildMaze()
    }
}

struct SettingsView : View {
    @ObservedObject var myMaze = Maze.sharedInstance
    @State private var isEditing1 = false
    @State private var isEditing2 = false
    var body : some View {
        HStack(alignment: VerticalAlignment.top) {
            VStack(alignment: .leading) {
                Text("Settings\n") .font(.title)
                Text("Number of rooms (horizontally): \(myMaze.widthDouble, specifier: "%.0f")")
                    .foregroundColor(isEditing1 ? .red : .black)
                Slider(value: $myMaze.widthDouble, in: 3...32, step: 1,
                       onEditingChanged: { editing in isEditing1 = editing })
                Text("\nNumber of rooms (vertically): \(myMaze.heightDouble, specifier: "%.0f")")
                    .foregroundColor(isEditing2 ? .red : .black)
                Slider(value: $myMaze.heightDouble, in: 3...32, step: 1,
                       onEditingChanged: { editing in isEditing2 = editing })
                Text("\nIn a maze with only one way, you can find the exit by touching the wall or hedge with the hand nearest to it, left or right. Keep that same hand touching the wall and keep walking. This may take you on a horribly long route, but it will get you out.")
                Toggle("One way", isOn: $myMaze.oneWay)
                Spacer()
                Button(action: { action() }) { Text("Build maze") }
                    .padding()
                    .background(Color.red)
                    .cornerRadius(20)
                    .foregroundColor(.white)
                    .font(.title)
                    .shadow(color: .gray, radius: 20.0, x: 10, y: 10)
                Spacer()
            } .padding()
        }
    }
    func action() {
        myMaze.buildMaze()
    }
}
juud5qan

juud5qan1#

您的控制台输出不是您所期望的原因是由于VStack和HStack容器中循环的顺序。
在你的VStack中,你有一个从0到myMaze.height - 1的y循环。在这个循环中,你有一个HStack,它包含一个从0到myMaze.width - 1的x循环。
这意味着HStack循环(for x)将在VStack循环(for y)的每次迭代中执行,这将为每个y值创建一行DrawRoom视图。但是,由于您使用myMaze.mazeData[xPos][yPos]访问mazeData数组,因此x值的变化速度比y值快,这导致行以意外的顺序打印。
为了解决这个问题,你应该切换循环的顺序,这样HStack循环(针对x)首先对每个y值执行,然后再移动到下一行:

VStack(spacing: 0) {
    ForEach(0..<myMaze.width, id: \.self) { (x: Int) in
        HStack(spacing: 0) {
            ForEach(0..<myMaze.height, id: \.self) { (y: Int) in
                DrawRoom(xPos: x, yPos: y)
            } // ForEach y
        } // HStack
    } // ForEach x
} // VStack

通过这个更改,DrawRoom视图将被创建并逐行打印,从上到下,从左到右,这应该会产生您所期望的控制台输出。

相关问题