Learn Courses My Dashboard

Andy's iOS Journal

Day-28-StockPickerD7

  • May 22, 2021
  • Day 7 of the StockPickerChallenge

I haven’t had much time over the last couple of weeks to make progress.

The current version solves the basic challenge but I wanted to download stock history and have that get incorporated in a stockInfo vew. Unforutately, there is something simple that I am missing that I can’t quite get the date: closingValue information out of the json and into a [Double] rather than it coming back as a dict or some other object I haven’t figured out what.

Either way I got out of this what I wanted, I am going back to tutorial grinding and personal projects for a while.

Day-29-SwiftUI-and-spritekit

  • Jun 9, 2021
  • SpriteKit and SwiftUI

Objective for this morning is to create the code organization for a simple SpriteKit/SwiftUI App. I am going to use the code from Paul at HWS as a starting point.

There are really 2 problems that we need to address.

  1. How do we make a spritekit scene into a view that swiftui can use.
  2. How do we organize it so that it doesn’t end up all in a single file.

The first point is pretty well solved with the tutorial mentioned above and just works.

DropBoxView.swift

import SwiftUI
import SpriteKit

struct BoxDropView: View {

    var scene: SKScene {
            let scene = BoxDropScene()
            scene.size = CGSize(width: 300, height: 400)
            scene.scaleMode = .fill
            return scene
        }

    var body: some View {
        VStack {
            Text("Box Drop Game")
                .bold()
                .scaleEffect(CGSize(width: 2.0, height: 2.0))

            SpriteView(scene: scene)
                .frame(width: 300, height: 400)
                .ignoresSafeArea()
        }

    }
}

The main points here are that we define a scene variable that is of type SKScene. Inside this SKScene is an instance of the spritekit scene (BoxDropScene).

In the tutorial, that class is in the same file but it doesn’t have to be. So the code above by itself will error because we haven’t defined the BoxDropScene class

BoxDropScene.swift

This is pretty much from the tutorial example, It makes a box that falls when you tap. I decreased the size of the boxes to make it a little more interesting and gave it a physicsBody that is just slightly larger than the box so the boxes don’t actually touch.

import SpriteKit


class BoxDropScene: SKScene {
    override func didMove(to view: SKView) {
        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let location = touch.location(in: self)
        let box = SKSpriteNode(color: SKColor.red, size: CGSize(width: 20, height: 20))
        box.position = location
        box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:22, height: 22))
        addChild(box)
    }
}

Folder organization

  • View
    • MainScreen.swift
    • BoxDropview.swift
  • SpriteKitScenes
    • BoxDropScene.swift

Down the road we may want the following for score data etc.

  • Model
  • ViewModel

MainScreen.swift

For the main screen, I am using NavigationLink and NavigationView to create a text button that will redirect the user to the BoxDropView() which contains the BoxDropScene written in SpriteKit.

import SwiftUI
import SpriteKit




struct MainScreen: View {
    var body: some View {
        NavigationView {
            ScrollView {

                NavigationLink(
                    destination: BoxDropView(),
                    label: {
                        Text("Box Drop Game")
                    })//NavigationLink

            }//ScrollView
        }//NavigationView
    }//Body
}//Struct End

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

With this template, we can now design the App in SwiftUI and wrap the SpriteKit into scenes that we can display using SwiftUI’s SpriteView. We could make any number of games and put them in a list or as cards that we can swipe through.

Day-29-SwiftUI-and-spritekit

  • Jun 9, 2021
  • Geometry reader

Next up is can I incorporate geometryReader into the SpriteView so that it fills up the available space? The main issue I was having is that when rotated, the scene was causing the boxes to elongate and weren’t boxes anymore as the scene was stretched. That won’t be good to go to production.

Solution

MainScreen.swift

  1. Created a @State variable in the MainScreen.swift
@Binding var geoSize: CGSize
  1. Added an .onRecive(notificationCenter) after Navigation view that updates the geoSize binding upon a device change orientation notification.

  2. Added a function that sets the Geometry size.

private func saveGeoSize(for geo: GeometryProxy){
    let size = CGSize(width: geo.size.width, height: geo.size.height)
    self.geoSize = size
    print(self.geoSize)
}
struct MainScreen: View {
    @State var geoSize = CGSize(width: 30, height: 40)
    @State var reOrient = false

    var body: some View {
        GeometryReader { geo in
                NavigationView {
                    ScrollView {

                        NavigationLink(
                            destination: BoxDropView(geoSize: $geoSize, reOrient: $reOrient),
                            label: {
                                Text("Box Drop Game")
                            })//NavigationLink

                    }//ScrollView
                }//NavigationView
                .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
                    self.reOrient = true
                    self.saveGeoSize(for: geo)

                }
        } //GeometryReader
    }//Body

    private func saveGeoSize(for geo: GeometryProxy){
        let size = CGSize(width: geo.size.width, height: geo.size.height)
        self.geoSize = size
        print(self.geoSize)
    }
}//Struct End

BoxDropView

  1. added a @Binding
@Binding var geoSize: CGSize

  1. Updated the scene.size and SpriteView().frame variables
scene.size = CGSize(width: self.geoSize.width*0.9, height: geoSize.height*0.9)
SpriteView(scene: scene)
                                .frame(width: geo.size.width*0.9, height: geo.size.height*0.9)
                                .ignoresSafeArea()

references

Remaining issues

  • The scene resets every time the notification is activated. The binding updates which the updates the scene by creating a new BoxDropScene, which is a class so all children (boxes) get reset every time you reorient. I could just force it to be in one or the other mode but I am curious to see what other options are possible. I suppose I could also make it so upon rotation the frame doesn’t change from the original size. But both of these are not terribly satisfying.

Day-29-Shared-Instances

  • Jun 9, 2021
  • Shared instances

Shared instances appear to solve most of the issue I am having but not fully.

Solution

  1. Add a shared instance to the BoxDropScene class
class BoxDropScene: SKScene {
    static let shared = BoxDropScene()
  1. Change the instance of the BoxDropScene upon creation in BoxDropView.swift from
var scene: SKScene {

        let scene = BoxDropScene()

to

var scene: SKScene {
        let scene = BoxDropScene.shared

Remaining Issues

The children in the box can be lost when going from landscape to portrait and the State variable is in the mainScreen, which means the geoSizes (portrait or landscape) is set on that screen.

Question

Can I keep users from switching after entering the game? – Looks like this is possible though may be slightly more complicated to pull off. See SwiftUI: Force orientation on a per screen basis
for more details.

References

spritekit-transition-between-scenes-without-resetting-game

I think if I make a game I will just decide to have it in one orientation if I am going to