SwiftUI: Getting Started | raywenderlich.com

Is there any chance to try SwiftUI with Mojave 10.14.5 and Xcode 10.2.1?

you can build and run SwiftUI apps in Xcode 11 beta on Mojave 10.14.5 ā€” you just wonā€™t see the previews; youā€™ll have to run in the simulator to see your UI

the tutorial has a link to Appleā€™s instructions for installing Catalina on a volume (not partition), so you donā€™t have to allocate a specific amount of space for it, and you can delete the volume when you donā€™t need it anymore. Scroll down to see the instructions for switching between Catalina an Mojave ā€” I found these more reliable than holding down the Option key when restarting.

At the end of the tutorial you show landscape preview in a simulator. Do you know how to test for device rotation in SwiftUI?

sorry, I donā€™t know ā€¦ have you tried UIKitā€™s UIDeviceOrientation, to see if it works with SwiftUI?

I just noticed SceneDelegate.swift imports UIKit, so that might be somewhere you can test for orientation?

You can get size classes from Environment, which is the preferred way even in UIKit. If you absolutely need device orientation, you could grab it in SceneDelegate and insert it into the Environment so your SwiftUI code can retrieve it.

// @audrey

Label shrinks when I tried to rest the slider values

Any suggestion ?

Full Code

// ContentView.swift
// GuessTheColor
// Created by Prashant on 13/07/19.


import SwiftUI 
import Combine

struct ContentView : View {
    
    @ObjectBinding var sliderBinder = SliderBindable()
    
    @State var rTarget  = Double.random(in: 0..<1)
    @State var gTarget = Double.random(in: 0..<1)
    @State var bTarget = Double.random(in: 0..<1)

    @State private var needToShoWAlert = false
    @State private var score = 0

    var body: some View {

        NavigationView {
            VStack {
                HStack(spacing:6) {
                    VStack {
                        Rectangle().foregroundColor(Color(red: rTarget  , green: gTarget  , blue: bTarget ))
                        Text("Target color")
                        
                    }
                    VStack {
                        Rectangle().foregroundColor(Color(red: sliderBinder.r  , green: sliderBinder.g  , blue: sliderBinder.b ))
                        
                        HStack {
                            Text("R: \(Int(sliderBinder.r * 255.0))")
                            Text("G: \(Int(sliderBinder.g * 255.0))")
                            Text("B: \(Int(sliderBinder.b * 255.0))")
                        }
                    }
                    
                    }.padding(6)
                
                SliderView(sliderBinder: sliderBinder)
                
                Button(action: {
                    self.score = self.computeScore()
                    
                    self.resetTheColor()
                    
                    self.needToShoWAlert = true
                    
                    
                }) {
                    Text("Match me")
                }
                    .clipped()
                .frame(width: 190, height: 60)
                    .border(Color(red: rTarget  , green: gTarget  , blue: bTarget ),width:3,cornerRadius: 12)
                    .foregroundColor(Color(red: rTarget  , green: gTarget  , blue: bTarget ))
               
                
                Spacer()
                
                }.presentation($needToShoWAlert) {
                    Alert(title: Text("Your Guess"), message: Text("\(score)"), dismissButton: .default(Text("OK")))
                
                }.navigationBarTitle(Text("Match the color"))
        }

    }
    
    func resetTheColor () {
        rTarget  = Double.random(in: 0..<1)
        gTarget = Double.random(in: 0..<1)
        bTarget = Double.random(in: 0..<1)
        
        sliderBinder.reset()
    }
    
    func computeScore() -> Int {
        let rDiff = sliderBinder.r - rTarget
        let gDiff = sliderBinder.g - gTarget
        let bDiff = sliderBinder.b - bTarget
        
        
        let diff = sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff)
        return Int((1.0 - diff) * 100.0 + 0.5)
    }
}






class SliderBindable:BindableObject {
    
    var r:Double = 0 {
        didSet {
            didChange.send((r,g,b))
        }
    }
    var g:Double = 0 {
        didSet {
            didChange.send((r,g,b))

        }
    }
    var b:Double = 0 {
        didSet {
            didChange.send((r,g,b))

        }
    }
    
    var didChange = PassthroughSubject<(r:Double,g:Double,b:Double),Never>()

    
    
    func reset() {
        r = 0
        g = 0
        b = 0
    }
    
    
}




struct SliderView: View {

    @ObjectBinding var sliderBinder = SliderBindable()
    
    var body : some View {
        VStack {
            HStack {
                Text("0").foregroundColor(.red)
                Slider(value: $sliderBinder.r)
                Text("255").foregroundColor(.red)
                }.padding()
            HStack {
                Text("0").foregroundColor(.green)
                Slider(value: $sliderBinder.g)
                Text("255").foregroundColor(.green)
                }.padding()
            HStack {
                Text("0").foregroundColor(.blue)
                Slider(value: $sliderBinder.b)
                Text("255").foregroundColor(.blue)
                }.padding()
            
           
            
        }.padding()
        
    }
    
    
}


#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

hi Prashant: the labels are just using their ā€œintrinsicā€ size ā€” they make themselves the right size for what they contain. When you move the sliders in live preview, the labels adjust to show the correct values.

In my sample project, I reset the values to 0.5, to center the sliders. This has the side effect of keeping the labels wide enough to fit 3 digits (127) for each color. Itā€™s what the original BullsEye game does, to let the user start from the middle value. For this game, it makes the guess block gray instead of black.

Hi @audrey, This is a good introduction to SwiftUI.

Because it is a View, I tried extracting the ColorSlider into its own file and added a preview as follows:

#if DEBUG
struct ColorSlider_Previews: PreviewProvider {
static var previews: some View {
return ColorSlider(value: 0.5, textColor: .red)
}
}
#endif

However, I get the error ā€œCannot convert value of type ā€˜Doubleā€™ to expected argument type ā€˜Binding<Double>ā€™ā€. For the purpose of running a preview, how do you declare a Binding<Double> with a value of 0.5?

Vince.

hi Vince, excellent question! Iā€™m writing a UIViewRepresentable version that uses UISlider, and wanted to preview it like Tanu does in the WWDC video ā€” notice she didnā€™t show the actual code for previewing her stars!

something like this gets rid of all the error messages:

<del>@State static var value = 0.5</del>
static var previews: some View {
  ColorSlider(value: .constant(0.5), textColor: .red)
}

but the preview wonā€™t refresh ā€” it says the app may have crashed :unamused:

I had a look through Mastering Xcode Previews, but didnā€™t see any solution. and he snuck in that .constant trick Iā€™ve just updated in the code above. It works!

Thanks for the question! :+1:

1 Like

It doesnā€™t work with X11b5. I anticipated as much, however, you still should at the beta version number for Xcode 11 so people know which version the code works with. Better: update the code for the latest beta.

hi Adrian, thanks for the heads up! at this stage, SwiftUI is changing every couple of weeks, so Iā€™ll wait until the final v1 release before updating the code.

Beta 5 is much less helpful than b4, which offered to make the necessary changes:

  • .presentation($showAlert) becomes .alert(isPresented: $showAlert)
  • .color(textColor) becomes .foregroundColor(textColor)

all the other error messages are just Xcode being confused by these two syntax changes.

hi Adrian: Iā€™ve updated the tutorial and project to Xcode 11 GM.

Wow, I just caught myself playing the BullsEye Game for two hours :neutral_face: :joy:

1 Like

I must admit I spent more time ā€œtestingā€ it than was really necessary :wink:

1 Like

audrey, can you please tell me how I could restart the game? This is my first SwiftUI tutorial, I guess it must be something like reinitializing the whole view? Because when I try to change my target values somewhere the compiler tells me I cannot mutate anything because self is immutableā€¦ :thinking:

EDIT:
Oh okay, changing the target color values to @State vars did the trick! :partying_face: For anybody else wondering: Call this func from within your Alerts Dismiss Button Action.

   func restartGame() {
        rTarget = Double.random(in: 0..<1)
        gTarget = Double.random(in: 0..<1)
        bTarget = Double.random(in: 0..<1)
        
        rGuess = 0.5
        gGuess = 0.5
        bGuess = 0.5
    }

Spot on!

Makes it a lot easier to keep playing :smiling_imp:

Hi @audrey, Iā€™ve noticed that in your example the main VStack appears to be stretched to a full height.
But when Iā€™ve run my own code or tried to run the finished example the VStack takes a portion of the screenā€™s height.
I havenā€™t found any solution online unfortunately, do you have any idea?

Xcode Version 11.0 (11A419c)

hi Oron! try adding Spacer():

var body: some View {
  VStack {
    Text("Hello World")
    Spacer()
    Text("Hello World")
    Spacer()
    Text("Hello World")
  }
}

Or, do you mean that my RGBullsEye code doesnā€™t cover the screen?

Hi, thanks for your response Audrey,
I was talking about RGBullsEye, and I well know the Spacer but I donā€™t think itā€™s the solution here.
Since in your example it seems that the Rectangle takes over the entire remaining height of the screen but it just donā€™t happen, not in the finished example and not when I try to code it myself.

Iā€™ve just downloaded the project from the tutorial page and opened it in Xcode 11.1 on Catalina:

32

The problem might be with your installation of Xcode? Try restarting Xcode or Mac, or reinstall Xcode. Delete any earlier versions of Xcode, too.