This question already has an answer here:
How can I pass 2 content parameters to a SwiftUI View?
(1 answer)
Closed 6 months ago.
In Swift/SwiftUI, I'm trying to make a custom view, which takes other views as arguments. However, I'm getting the error Type of expression is ambiguous without more context.
I know that this error is usually due to mismatching types. Also, in the implementation below, having 2 Text views causes no error, but using a Text view as well as an Image view causes an error.
This makes me believe that the error is due to the fact that Image and Text are technically different types. However, since they both conform to View, and because my CustomView has both parameters of type View, I don't understand why this issue is happening.
struct CustomView<Content: View>: View {
#ViewBuilder var label: Content
#ViewBuilder var content: Content
var body: some View {
VStack {
self.label
self.content
}
}
}
struct ContentView: View {
var body: some View {
CustomView { //<-- ERROR: Type of expression is ambiguous without more context
Image(systemName: "person.3.fill")
} content: {
Text("")
}
}
}
What am I doing wrong, and how can I fix this error? Thanks in advance!
The problem with your code is that in CustomView you defined both label and content as views of a single type (generic Content), but you pass different kinds to the initializer.
One way to fix it is to define different types for label and content:
struct CustomView<Label: View, Content: View>: View {
#ViewBuilder let label: Label
#ViewBuilder let content: Content
// The rest is unchanged.
Related
The following code is simplified and isolated. It is intended to have a Text() view, which shows the number of times the button has been clicked, and a Button() to increment the text view.
The issue: Clicking the button does not actually change the Text() view, and it continues to display "1"
struct Temp: View {
#State var h = struct1()
var body: some View {
VStack {
Button(action: {
h.num += 1
}, label: {
Text("CLICK ME")
})
Text(String(h.num))
}
}
}
struct struct1 {
#State var num = 1
}
What's funny is that the same code works in swift playgrounds (obviously with the Text() changed to print()). So, I'm wondering if this is an XCode specific bug? If not, why is this happening?
Remove #State from the variable in struct1
SwiftUI wrappers are only for SwiftUI Views with the exception of #Published inside an ObservableObject.
I have not found this in any documentation explicitly but the wrappers conform to DynamicProperty and of you look at the documentation for that it says that
The view gives values to these properties prior to recomputing the view’s body.
So it is implied that if the wrapped variable is not in a struct that is also a SwiftUI View it will not get an updated value because it does not have a body.
https://developer.apple.com/documentation/swiftui/dynamicproperty
The bug is that it works in Playgrounds but Playground seems to have a few of these things. Likely because of the way it complies and runs.
By accident i added a modifier to my swift UI view without a leading point. And it compiled. And i can not wrap my head around why i did that.
Here some example Code:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: DetailView(),
label: {
Text("Goto Detail")
})
navigationTitle("ContentView")
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail View")
.padding()
navigationTitle("Detail")
}
}
Somehow even stranger is that the first "wrong" modifier navigationTitle("ContentView") does just nothing.
The second one navigationTitle("Detail") lets the App crash when navigating to the View during runtime.
Similar to this is
struct DetailView: View {
var body: some View {
padding()
}
}
This View does compile but just crashes, if tried to shown with Previews. And it just can't be navigated to.
I would really expect this code to not even compile.
Is somebody able to explain this?
If you refer to a method by its simple name (e.g. navigationTitle), it's kind of like saying self.navigationTitle:
struct DetailView: View {
var body: some View {
Text("Detail View")
.padding()
self.navigationTitle("Detail")
}
}
This is valid, because self is also a View, and that modifier is available on all Views. It gives you a new view that is self, but with a navigation title of Detail. And you are using both of them as the body. Normally you can't return multiple things from a property, but it works here because the body protocol requirement is marked #ViewBuilder.
Of course, you are using self as the self's body, and you can't have self referencing views, so it fails at runtime.
If you add self. to the other cases, it's pretty easy to understand why they compile:
struct DetailView: View {
var body: some View {
self.padding() // self conforms to View, so you can apply padding()
// padding() returns another View, so it's valid to return here
}
}
"The body of DetailView is itself, but with some padding."
NavigationView {
NavigationLink(
destination: DetailView(),
label: {
Text("Goto Detail")
})
self.navigationTitle("ContentView")
}
"The navigation view consists of a navigation link, and ContentView itself (what self means here) with a navigation title of ContentView"
This doesn't crash immediately either because NavigationView's initialiser is smart enough to ignore the junk ContentView that you have given it, or because there is an extra level of indirect-ness to the self-referencing. I don't think we can know exactly why it crashes/doesn't crash immediately, until SwiftUI becomes open source.
I'm trying to pass a view into a struct for creating a tab which is the following code:
struct TabItem: TabView {
var tabView: View
var tabText: String
var tabIcon: String
var body: some View {
self.tabView.tabItem {
Text(self.tabText)
Image(systemName: self.tabIcon)
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
TabItem(tabView: SimpleCalculatorView(), tabText: "Home", tabIcon: "house")
}
}
}
}
The error I'm getting is the following:
Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
The error says here is that the View protocol has associatedtype Body inside it and thus you can't use the View as a type. This means we have to provide more information about the expected type of the tabView. One of the solutions would be to use AnyView as a type, but that would require additional content wrapping into AnyView.
What I would suggest doing here instead of using AnyView is to let the compiler figure out the actual tabView type for you.
Let's tell the compiler that we expect some type Content that confirms to the View protocol
Additionally I don't really see the necessity of using TabView as part of the TabItem declaration. Try going with just View unless you have a strong reason for not doing so.
struct TabItem<Content: View>: View { // Content is a type that we expect. `View` is used instead of `TabView` as it is in original code
var tabView: Content // Using Content instead of a View as it is an actual type now
...
}
The rest of the code can stay unmodified.
I am experimenting building a pretty simple SwiftUI program to swap views easily. The goal is to make it as simple as possible to add more views without having to change the code that determines which view is how (so a standard if-else isn't going to work).
My current thinking is to keep a dictionary with views stored as part of a key-value pair. A very basic example of the implementation is as follows:
import SwiftUI
struct ViewA: View {
var body: some View {
Text("This is View A")
}
}
struct ViewB: View {
var body: some View {
Text("This is View B")
}
}
struct MainView: View {
var subviews: [String:View] = [
"View-1": ViewA(),
"View-2": ViewB(),
]
var body: some View {
self.subviews["View-1"]
}
}
I am however getting an error on the lines where I am creating the dictionary: Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements.
I have tried a number of different types for the value part of the dictionary, including AnyView, Groups, and making a generic type <Content: View> on the MainView struct. These however give more/different errors.
I have looked at the project SwiftUIRouter, as it kind of solves what I a trying to achieve, however my goals are a bit more simplistic.
Thanks in advance.
Swift doesn't have named subscripts, so you have to put it together hackily, but you can use the same calling syntax with otherwise useful/better language features:
struct MainView: View {
var body: some View {
subviews["View-1"]
}
enum subviews {
#ViewBuilder static subscript(string: String) -> some View {
switch string {
case "View-1":
ViewA()
case "View-2":
ViewB()
default:
fatalError()
}
}
}
}
I searched a lot about this error but it seems there's no solution...
UITableView was told to layout its visible cells and other contents without being in the
view hierarchy (the table view or one of its superviews has not been added to a window).
This may cause bugs by forcing views inside the table view to load and perform layout without
accurate information (e.g. table view bounds, trait collection, layout margins, safe area
insets, etc), and will also cause unnecessary performance overhead due to extra layout passes.
Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in
the debugger and see what caused this to occur, so you can avoid this action altogether if
possible, or defer it until the table view has been added to a window
This is my actual code:
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: FavoriteBooks.getAllFavoriteBooks()) var favoriteBooks:FetchedResults<FavoriteBooks>
#ObservedObject var bookData = BookDataLoader()
var body: some View {
NavigationView {
List {
Section {
NavigationLink(destination: FavoriteView()) {
Text("Go to favorites")
}
}
Section {
ForEach(0 ..< bookData.booksData.count) { num in
HStack {
Text("\(self.bookData.booksData[num].titolo)")
Button(action: {
**let favoriteBooks = FavoriteBooks(context: self.managedObjectContext)
favoriteBooks.titolo = self.bookData.booksData[num].titolo**
}) {
Image(systemName: "heart")
}
}
}
}
}
}
}
}
struct FavoriteView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: FavoriteBooks.getAllFavoriteBooks()) var favoriteBooks:FetchedResults<FavoriteBooks>
var body: some View {
List {
**ForEach (self.favoriteBooks) { book in
Text("\(book.titolo!))")**
}
}
}
}
I just selected on bold what makes this error and I don't know how to avoid it because if I launch the app it doesn't crash but I cannot do anything.
Thanks in advance
You have a couple issues here. The first is, when you use ForEach, if your content is supposed to be able to change (which, with FetchRequest it is...) then either FavoriteBooks needs to be Identifiable or you need to pass in your id. You actually do this twice in the code:
ForEach(0 ..< bookData.booksData.count) { num in
// SwiftUI thinks this content never changes because it doesn't know how to resolve those changes. you didn't tell it
}
should be:
ForEach(0 ..< bookData.booksData.count, id: \.self) { ... }
Notice now you are telling it what the id is. If the count of bookData.booksData changes, now SwiftUI can resolve those changes. But really, why do you need the index in this case specifically? Why not just:
ForEach(bookData.booksData) { book in ... }
If you make this object type conform to Identifiable, you now have the book already.
Now on to another problem, your button action. Why are you re-executing the CoreData query here? You have the Set of the objects you want. This is another reason to just use ForEach(bookData.booksData), you don't have to resolve the index here. But in general, you should never need to re-execute your core data query to find a specific object. what this actually does is then trigger another update on your entire view hierarchy, which is likely why you get the error you're getting. you aren't supposed to be doing this.