Handling if statement in when setting view properties - ios

I'm having a funky bug that's got me stuck for a bit. I simply want to adjust the padding on a SwiftUI view based on the value of a variable (in this case, a variable that determines if we're in dark mode or not). Here's my code:
LazyHGrid(rows: rows, spacing:10) {
// Other stuff here
}.padding(colorScheme == .dark ? [.top, .bottom] : [])
My color scheme variable is defined as
#Environment(\.colorScheme) var colorScheme
But I keep getting an error on the line with the padding code that says:
"Cannot convert value of type '(ColorScheme) -> some View' to expected argument type 'ColorScheme'"
What do I do?

Related

How to use optional #State variable with binding parameter in SwiftUI

I am trying to use an optional #State variable with a TextField. This is my code:
struct StorageView: View {
#State private var storedValue: String?
var body: some View {
VStack {
TextField("Type a value", text: $storedValue)
}
}
}
When I use this code, I get the following error:
Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding<String>'
I have tried the following code for both the values to be optional, but nothing seems to work:
TextField("Type a value", text: $storedValue ?? "")
TextField("Type a value", text: Binding($storedValue)!)
How would I go about using an optional #State variable with a binding? Any help with this is appreciated.
How would I go about using an optional #State variable with a binding? Any help with this is appreciated.
It looks like you are using an optional state variable with a binding. You get an error because TextField's initializer expects a Binding<String> rather than a Binding<String?>. I guess you could solve the problem by adding another initializer that accepts Binding<String?>, or maybe making an adapter that converts between Binding<String?> and Binding<String>, but a better option might be to have a good think about why you need your state variable to be optional in the first place. After all this string something that will be displayed in your UI -- what do you expect your TextField to display if storedValue is nil?

SwiftUI View starter code complains about `inheritance from non-protocol type 'View'` when added to an existing project

I have an existing Xcode project. I need to create a new view. I was going to use File > New > File... > View, but then I noticed there is another option SwiftUI View. So I decided to give it a shot. I went ahead and chose SwiftUI View.
It created the following starter code:
import SwiftUI
struct FooBar: View {
var body: some View {
Text("Hello, World!")
}
}
struct FooBar_Previews: PreviewProvider {
static var previews: some View {
FooBar()
}
}
And it has the following compiler errors:
I tried cleaning, and selecting the iPhone 11 Pro simulator as the destination target, but it still has the same compiler errors.
For search-ability, here are all the errors:
Inheritance from non-protocol type 'View' (aka 'UIView')
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Cannot convert return expression of type 'Text' to return type 'some View'
Type 'FooBar_Previews' does not conform to protocol 'PreviewProvider'
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Cannot convert return expression of type 'FooBar' to return type 'some View'
I'm on macOS Catalina, Xcode 11.2.1, and the deployment target is iOS 13.0.
What am I doing wrong?
It seems to us there is a typealias View = UIView some where to mess the View with UIView.

How can I unwrap an optional value inside a binding in Swift?

I'm building an app using SwiftUI and would like a way to convert a Binding<Value?> to a Binding<Value>.
In my app I have an AvatarView which knows how to render an image for a particular user.
struct AvatarView: View {
#Binding var userData: UserData
...
}
My app holds a ContentView that owns two bindings: a dictionary of users by id, and the id of the user whose avatar we should be showing.
struct ContentView: View {
#State var userById: Dictionary<Int, UserData>
#State var activeUserId: Int
var body: some View {
AvatarView(userData: $userById[activeUserId])
}
}
Problem: the above code doesn't combine because $userById[activeUserId] is of type Binding<UserData?> and AvatarView takes in a Binding<UserData>.
Things I tried...
$userById[activeUserId]! doesn't work because it's trying to unwrap a Binding<UserData?>. You can only unwrap an Optional, not a Binding<Optional>.
$(userById[activeUserId]!) doesn't work for reasons that I don't yet understand, but I think something about $ is resolved at compile time so you can't seem to prefix arbitrary expressions with $.
You can use this initialiser, which seems to handle this exact case - converting Binding<T?> to Binding<T>?:
var body: some View {
AvatarView(userData: Binding($userById[activeUserId])!)
}
I have used ! to force unwrap, just like in your attempts, but you could unwrap the nil however you want. The expression Binding($userById[activeUserId]) is of type Binding<UserData>?.

Change #State in a #ViewBuilder closure

I'm trying to understand how to change #State variables in #ViewBuilder closures. The following is just a simple example:
struct ContentView: View {
#State var width = CGFloat(0)
var body: some View { //Error 1
GeometryReader { geometry in //Error 2
self.width = geometry.size.width
return Text("Hello world!")
}
}
}
I'm getting some errors:
Error 1:
Function declares an opaque return type, but has no return statements
in its body from which to infer an underlying type
But the return is redundant because there's only one line of code inside the View computed property.
Error 2:
Cannot convert return expression of type 'GeometryReader<_>' to return
type 'some View'
Since I explicitly write return Text("...") shouldn't the type be clear?
What's the problem here?
First, you can't make arbitrary swift statements in a functionBuilder. There is a specific syntax allowed. In SwiftUI's case, it's a ViewBuilder, which under the hood is composing your type. When you make consecutive statements in SwiftUI, you are actually relying on the compiler to compose a new type underneath according to the rules of that DSL.
2nd, SwiftUI is a recipe, it's not an event system. You don't change state variables in the body function, you set up how things should react when state variables change externally. If you want another view to react in some way to that width, you need to define that content with respect to the width of the component you want. Without knowing what your ultimate goal is here, it's hard to answer how to relate the content to each other.
EDIT:
I was asked to elaborate on what exactly is allowed. Each functionBuilder has a different allowable syntax which is defined in the functionBuilder itself. This has a good overview on function builders in Swift 5.1: https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api
As for what SwiftUI is specifically looking for, it's essentially looking for each statement to return an instance of View.
// works fine!
VStack {
Text("Does this")
Text("Work?")
}
// doesn't work!
VStack {
Text("Updating work status...")
self.workStatus = .ok // this doesn't return an instance of `View`!
}
// roundabout, but ok...
VStack {
Text("Being clever")
gimmeView()
}
// fine to execute arbitrary code now, as long as we return a `View`
private func gimmeView() -> some View {
self.workingStatus = .roundabout
return Text("Yes, but it does work.")
}
This is why you got the obtuse error you got:
Cannot convert return expression of type 'GeometryReader<_>' to return type 'some View'
The type system can't construct any View out of View and essentially Void when you execute:
self.width = geometry.size.width
When you do consecutive View statements, underneath, it's still being converted into a new type of View:
// the result of this is TupleView<Text, Text>
Text("left")
Text("right")

Weird error in accessing the text of UIButton in swift

When I write a simple function such as this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel.text);
}
It gives me an error: UILabel doesn't have a label called text.
However, when I change it to this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel?.text);
}
It works fine, but it prints out something like this:
Optional("1");
What I am doing wrong? I am expecting a value of 1. But it is printing out Optional("1") and secondly, it is working fine when println(theButton.titleLabel?.text);
You can get directly from
let title = theButton.currentTitle!
Optional chaining makes the result optional, so you are printing optional value: https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/OptionalChaining.html
With optional binding you can print the value only if it exits.
if let text = theButton.titleLabel?.text {
println(text)
} else {
// text doesn't have value
}
#Kirsteins's answer shows how to obtain the button label text in a safe manner.
Remember that:
UIButton has a titleLabel, which is an optional UILabel.
UILabel has a text property, which is an optional String
so there are 2 optionals in the chain. You can use optional binding as in #Kirsteins's answer, or use forced unwrapping:
let text = theButton.titleLabel!.text!
which however I discourage using, because if any of the 2 is nil you'll have a runtime exception. But for completeness it's worth mentioning.
The buttons titleLabel property returns an optional UILabel, that means it's possible that the button doesn't have a titleLabel.
var titleLabel: UILabel? { get }
If you don't set a title to the button, then the button doesn't have a titleLabel property, the iOS framework adds the titleLabel only if the button has a title, I think this happens to reduce memory.
This is why you have to put the "?" (is called optional chaining you can read about it here http://bit.ly/1vrSOi1) in that line, but this usually get auto completed by Xcode itself.
Kirsteins answers it correctly but misses one small detail
if your object can be nil (optional) you need to check first if it exists to then access its value, like this:
if let text = theButton.titleLabel?.text {
println(text)
}
but you can also ignore the if and just call it like this:
let text : String = theButton.titleLabel?.text
// If theButton.titleLabel don't exists then text will be nil
this happen if the IBOutlet was declared with ? but if you declare with ! that means you know that it could be nil, but you never want it to be nil, for a IBOutlet i prefer this approach since if the IBOutlet is not connected then maybe something is worn with my code.
#IBOutlet var theButton : UIButton!
// and get text value as
theButton.titleLabel!.text
this will ensure theButton.titleLabel could be nil, but in this part of code it is required, hope this helps to understand the difference between optional (?) and optional required (!)

Resources