How to use Identifiable in a ForEach loop - swiftui

I am trying to iterate through an array of objects. This object is conforming to the Identifiable protocol. When using a ForEach loop, I get the following error: Type of expression is ambiguous without more context
I've included the block of code that is throwing the error. The error is specifically underlining \.name. Am I missing something?
Another note: This code worked in Xcode 11 Beta 2 but broke in Xcode 11 Beta 3...
struct ItemRow : View {
var categoryName:String
var items:[Item]
var body: some View {
VStack {
Text(self.categoryName)
.font(.title)
ScrollView(showsHorizontalIndicator: false) {
HStack (alignment: .top){
ForEach (self.items.identified(by: \.name)) { item in
NavigationLink(destination: ItemDetail(item: item)) {
ItemView(item: item)
.frame(width:300)
.padding(.trailing, 30)
}
}
}
}
}
}
}
Here is the Identifiable Object:
struct Item:Hashable, Codable, Identifiable {
var id:Int
var name:String
var category:Category
var description:String
}
(This code has been abstracted)

The first thing you need to know, is that when building views, compile errors can be very misleading. An error may show at the bottom of your code, but the cause may be at the top. I expect this will be fixed sometime in the future, but for the time being, you need to be careful.
Your code compiles just fine. Because of what I said about misleading errors, one brute but effective technique to debug the problem, is to start commenting bits of code until the error goes away. This will let pin point where the root of the problem may be.
A good way of updating your question is including enough code, so that people can reproduce the problem just by copy and paste into their own Xcode. It may be a lot of work for you, but I found that most of the time, you understand the problem during that process and you may not even need to post the question in the first place. Reducing an issue to its minimum expression, is also a great way of understading/fixing a problem.
UPDATE
Since you added more code, the error is showing where you would have not expected:
The ScrollView initialiser you were using, was deprecated. It now looks like this:
ScrollView(.horizontal, showsIndicators: false)
Also something that may potentially be a problem. You are using:
self.items.identified(by: \.name)
But don't you mean:
self.items.identified(by: \.id)
If so, then you do not need to use identified, since Item is already Identifiable and as such, it is already identified by id.
self.items

The issue was actually with the following line:
ScrollView(showsHorizontalIndicator: false)
ScrollView doesn't work like that anymore in Beta 3. The arguments now look something like this:
ScrollView(.horizontal, showsIndicators: false)
That will give you a horizontal scrolling view and will not show the scrolling indicators.

Related

Why does the following code get an error with a Date but not a String

The following code gets a compiler error "Variable 'self.d' used before being initialized"
it's easy to fix, by initializing the #State variable like you're supposed to, e.g.
_d = State(initialValue: test)
but I'm trying to understand the language better, and SwiftUI better, and don't see why it fails, but if I replace Date with String, it compiles just fine.
My best guess is that maybe the compiler is doing some optimization that is reforming the normally invalid syntax into something that works, and it can't do this in some cases.
I'm hoping someone else can give me a better idea about exactly what's going on. Thanks much for your attention :)
import SwiftUI
typealias Foo = Date
struct Test {
#State private var d: Foo
init(_ test: Foo) {
d = test // Variable 'self.d' used before being initialized
}
}
Edit: This works not just with primitive types, but a class that I define myself will work also, if it doesn't contain a Date.

SwiftUI: Why does ForEach need an ID?

Im using a ForEach loop in my SwiftUI View and I am getting strange warnings.
It works fine like this:
ForEach(0..<7) { i in
// do something
}
Then I changed 7 to a constant:
let numberOfElements = 7
ForEach(0..<numberOfElements) { i in
// do something
}
And got the following warning:
Non-constant range: argument must be an integer literal
I googled an found the following solution which works:
let numberOfElements = 7
ForEach(0..<numberOfElements, id:\.self) { i in
// do something
}
However, I have no idea why it works. Why do I have to give an ID to the ForEach loop, and what is the ID for?
ForEach(0..<numberOfElements) { i in
// do something
}
The reason why using the above ForEach init pops the using literal value warning is because SwiftUI doesn't expect to re-render anything when using the Range<Int> init method. This is a documented requirement / feature. The exceptions are the init methods with id:.
A hashable id matters in SwiftUI as well as in many other view-tree based frameworks like React is because these UI frameworks needs to use these ids to track updates for views inside the ForEach or any other "Container Views", to help the framework achieve usable performance. If you want to dig deeper, take a look at this WWDC video: Demystify SwiftUI.

Why .navigationTitle seams to throw UIViewAlertForUnsatisfiableConstraints warning while debugging on device?

If I run this code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
Text("Text")
.navigationTitle("My Title")
}
}
}
I get these warnings while debugging with my iPhone:
2021-03-15 18:00:08.023556+0100 Trial2[373:7602] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x283874dc0 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x103817930]-(6)-[_UIModernBarButton:0x102f2e3d0'My Title'] (active)>",
"<NSLayoutConstraint:0x283874e10 'CB_Trailing_Trailing' _UIModernBarButton:0x102f2e3d0'My Title'.trailing <= BackButton.trailing (active, names: BackButton:0x102f2d9a0 )>",
"<NSLayoutConstraint:0x283875b30 'UINav_static_button_horiz_position' _UIModernBarButton:0x103817930.leading == UILayoutGuide:0x282245f80'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x283875b80 'UINavItemContentGuide-leading' H:[BackButton]-(0)-[UILayoutGuide:0x282245ea0'UINavigationBarItemContentLayoutGuide'] (active, names: BackButton:0x102f2d9a0 )>",
"<NSLayoutConstraint:0x283858b90 'UINavItemContentGuide-trailing' UILayoutGuide:0x282245ea0'UINavigationBarItemContentLayoutGuide'.trailing == _UINavigationBarContentView:0x102f220c0.trailing (active)>",
"<NSLayoutConstraint:0x283876300 'UIView-Encapsulated-Layout-Width' _UINavigationBarContentView:0x102f220c0.width == 0 (active)>",
"<NSLayoutConstraint:0x283858a00 'UIView-leftMargin-guide-constraint' H:|-(0)-[UILayoutGuide:0x282245f80'UIViewLayoutMarginsGuide'](LTR) (active, names: '|':_UINavigationBarContentView:0x102f220c0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x283874dc0 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x103817930]-(6)-[_UIModernBarButton:0x102f2e3d0'My Title'] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
I've seen a similar question here, but the problem was that .navigationBarTitle is deprecated. In my case I'm using .navigationTitle. Is it also deprecated (I doubt it as the documentation doesn't say that)? Am I using it in a wrong way? Is it a bug of Xcode? Or simply is this normal and I shouldn't worry about these warnings?
P.S.: I'm new to Swift and SwiftUI programming
EDIT: #Andrew found something that seems to solve the issue (adding the modifier .navigationViewStyle(StackNavigationViewStyle()) to NavigationView). Yet, I don't understand if this is the right solution for a well defined issue in my code, or a trick to workaround an Xcode false positive/bug.
I would like someone to explain me why this code seem to fix my issue.
I don't think that .navigationViewStyle(StackNavigationViewStyle()) is the right solution, because the issue happened while following the Apple's tutorial. I think that if the error were expected, Apple would know it and put this line in the tutorial. So it is easier for me to believe it is an Xcode 14 bug.
To recap, my questions are:
Is the warning fired by Xcode expected? Or is it a bug?
Why .navigationViewStyle(StackNavigationViewStyle()) fixes the issue?
When is this warning really an issue? (I can't see anything wrong when I run it)

SwiftUI fails to build preview with compiling error

When I go to view my SwiftUI through the canvas preview in Xcode 11.3.1 I am getting the error
Compiling failed: 'Color' is only available in iOS 13.0 or newer
But the project itself builds successfully and the simulator loads without any issues. I have tried clearing the build folder, quitting Xcode and rebuilding but still no luck.
Any help would be great. Thanks in advance.
SwiftUI minimum deployment target is 13.0, so if you have project with support of older version, then all SwiftUI code (including preview providers) you have to prepend with availability modifier, like
#available(iOS 13.0, *) // << here !!
struct Demo: View {
var body: some View {
VStack {
Text("Hello")
}
}
}
You Should use Assets or Other option like Color Literal for Color.
Don't use the system Color option since your deployment target is 11.4.
Man, I've had a similar issue. The problem was that sometimes my preview worked sometimes it didn't... I reviewed the diagnostics and realized that there are some #_dynamicReplacement attributes mentioned (which are used, I guess, for hot reloading). It wasn't working when I've had a file with #available attributes opened in the (adjacent) editor. When I closed that editor everything worked back again.
Magic ✨
Also one more hint from my friend - when you have a file from another target (not the one hosting your Canvas-related code) in (adjacent) editor it behaves the same way.

Issue with declaring class variables

I'm implementing Google Maps in my project, because MKLocalSearch doesn't have a complete list of restaurants/bars. I'm following Google's documentation. I believe I've uncovered an issue and seek advice on how to solve.
The fist step is variable declaration at the class level:
var locationManager = CLLocationManager()
var currentLocation: CLLocation?
var mapView: GMSMapView!
var placesClient: GMSPlacesClient!
var zoomLevel: Float = 15.0
After following the remaining instructions, my program keeps failing on the mapView line:
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude,
longitude: defaultLocation.coordinate.longitude,
zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera) //Fails here!
The error message in the debugger is: Fatal error: Unexpectedly found Nil while unwrapping optional value.
So the question is why does the Google documentation declare the mapView variable the way it does? Don't all variables that ARE NOT optional have to initialize with a value? Should I declare the variable as Optional? Seems odd to me that Google Documentation would be incorrect.
Thanks.
By declaring an optional with ! you are telling the compiler whenever I access this optional it will have a value. It means you don't have to stick ! at the end everytime you use it. But it does mean if you haven't set the optional to have a value you will get an error when you access it.
An example of using one would a class member of a UIViewController that you setup in viewDidLoad(). You aren't required to setup a separate init() function for the view controller to initialise the variable but you can pretty much guarantee the entry point for your code is viewDidLoad() and any subsequent code run will have a valid version of this class member.
Search for Implicitly Unwrapped Optionals for more details
you should take optional mapView when there is possibility of having mapView or not. You should always take like this
let mapView = GMSMapView(frame: self.view.bounds)