I want my users to be able to define whether they want .iconOnly or .titleAndIcon behaviour for their label.
Unexpectedly to me though I am not able to apply on or the other style conditionally at the top of my view hierarchy:
Any suggestions on what needs to be done here?
Here is a possible approach. Tested with Xcode 13.4 / iOS 15.5
extension View {
#ViewBuilder
func labelStyle(includingText: Bool) -> some View {
if includingText {
self.labelStyle(.titleAndIcon)
} else {
self.labelStyle(.iconOnly)
}
}
}
and usage like
TabView {
// .. your code
}
.labelStyle(includingText: labelStyleShowText)
Related
I'm following a Kodeco tutorial(https://www.kodeco.com/4161005-mvvm-with-combine-tutorial-for-ios) for SwiftUI and Combine which hits an API and shows the data in a list. The tutorial doesn't explain how I can navigate to a detail view from the list, and I would like to implement this, but I'm having trouble adapting my code to do this.
My List is set up like this:
var body: some View {
NavigationView {
List {
characterSection
}
.navigationTitle("Characters")
}
}
Character Section:
var characterSection: some View {
Section {
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:))
}
}
I would like to be able to do something like the following to navigation to a detail screen, but I'm getting errors:
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:)) { item in
NavigationLink(destination: viewModel.characterDetailView) {
}
}
This obviously does not work but hopefully you can see what I am trying to do. I'm receiving these errors:
Generic parameter 'ID' could not be inferred,
Incorrect argument label in call (have ':content::', expected '_:id:content:')
Trailing closure passed to parameter of type 'KeyPath<Array.Element, ID>' that does not accept a closure
Here is my row view model in case the issue is with this, could be something to do with the ID?:
import Foundation
import SwiftUI
struct CharacterRowViewModel: Identifiable {
private let item: CharacterListResponse
var id: Int {
return item.char_id
}
var title: String {
guard let title = item.name?.description else { return "" }
return title
}
init(item: CharacterListResponse) {
self.item = item
}
}
Thanks for any help, I am new to SwiftUI.
One of your biggest problems is that your ForEach view is defining its contents twice, and the compiler (quite rightly) doesn't understand.
In the form you're using, ForEach takes two arguments:
the items to loop through
what view to generate for each item in the loop
That second argument is called content and it expects to be given something that receives a single item, and returns the View to use for that item.
When you use the form
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:))
you're passing in a function – in this case CharacterListRowView.init - that receives a row view model, and returns a CharacterListRowView.
That's convenient when the init method matches the expected function type. But the more common form is the type you're trying to switch to - a block that receives an item, and inside which you build the view for the item. When we use that form, we usually use Swift's trailing block syntax. This allows us to omit the argument name. So when we say
ForEach(items) { item in
// view code here
}
it's the same as
ForEach(items, content: { item in
// view code here
})
Now, look again at the code you've amended:
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:)) { item in
NavigationLink(destination: viewModel.characterDetailView) {
}
}
Here, you're not replacing the first form of content: with a block form, you're attempting to provide a second content block. The compiler is having a meltdown because you're providing two items to something that only expects one. When this happens, sometimes the compiler's error messages can get very confusing -- it reports how what it's tried to do to make your code work hasn't worked, rather than the direct cause of the failure.
So in your ForEach loop, remove the explicit content::
ForEach(viewModel.dataSource) { item in
NavigationLink(destination: viewModel.characterDetailView) {
}
}
You may still have problems after this step, but hopefully any error messages will be a little bit easier to understand.
Morning, everyone,
I'm developing a widget using an API that sends an array of different sizes depending on the size of the widget (small : 3 news, medium: 6 news, large: 9 news).
I have the impression that there is a problem between the #Environment(\.widgetFamily) var family and the timeline function (where I call the API) of TimelineProvider.
Indeed in this function the environment variable is always equal to the systemMedium size despite the "real" size of the widget.
Do you also have the same problem on your side ?
Is it a known bug from Apple ?
How can I solve this bug ?
Thanks for your help :]
Without seeing your code, my best guess is that you're not accessing the family property of the TimelineProviderContext passed into the TimelineProvider.
Your TimelineProvider should look something like:
struct MyProvider: TimelineProvider {
func snapshot(with context: Context, completion: #escaping (Entry) -> ()) {
fetchNewsArticles(for: context.family) { articles in
// ...
completion(...)
}
}
}
func fetchNewsArticles(for widgetFamily: WidgetFamily, completion: #escaping ... )
{
switch family {
case .systemSmall:
// ...
}
Apple Docs - TimelineProvider
Apple Docs - TimelineProviderContext
I am new to SwiftUI.... View is a protocol which only contains required stored property called "body". My question is , where modifier methods come from. 'cause "Protocol methods must not have bodies".?
My question is , where modifier methods come from. 'cause "Protocol methods must not have bodies".?
From extension like in below example:
extension View {
#ViewBuilder
public func isHidden(_ hidden: Bool) -> some View {
if hidden {
self.hidden()
}
else {
self
}
}
}
My apologies if this is not the right question to ask, as I am completely new to SwiftUI and iOS programming in general. The question indicates what I want to do, and the error I'm getting I believe is a red herring because of the SwiftUI compiler. It's likely that I am taking the incorrect approach to solving this problem altogether.
I am using XCode Version 11.2.1 (11B500)
View utilizing the ObservedObject:
struct Results: View {
var jobId: String
#ObservedObject var jobDetailService: JobDetailService
init(jobId: String) {
self.jobId = jobId
jobDetailService = JobDetailService(jobId: jobId)
}
var body: some View {
//... view code here
}
}
And it is within this view that I am getting the error (at the ZStack line) "Generic parameter 'C0' could not be inferred". When I comment out the NavigationLink block, the error goes away. Further, when the Results view does not depend on the jobId parameter (and we construct JobDetailService inline with #ObservedObject var jobDetailService = JobDetailService(), this all works. However, I need to be able to pass the jobId parameter to the JobDetailService in order to make the network call to fetch and publish the data.
struct JobList: View {
#ObservedObject var jobListService = JobListService()
var body: some View {
NavigationView {
List(jobListService.jobs) {job in
ZStack {
JobCard(name: job.fullName, date: job.lastUpdated)
NavigationLink(destination: Results(jobId: job.jobId)) {
EmptyView()
}
}
}
}
}
}
After reading this article, and thinking about Asperi's advice on not solely relying on initialization, I opted to do the following:
Remove custom initializer from JobDetailService and instead instantiate the service inside my Results view. Then in an .onAppear method on the Results view, call the getJobDetail method from JobDetailService which in turn makes the network call that populates the #ObservedObject. This allows me to pass in the parameters I need and control when the network call is made. Maybe not the right pattern for this problem but it works for my use case for now.
I assume the following should help you:
Declaration...
struct Results: View {
#ObservedObject var jobDetailService: JobDetailService
var body: some View {
//... view code here
}
}
... and usage
NavigationLink(destination: Results(jobDetailService: JobDetailService(jobId: jobId))) {
EmptyView()
}
I cannot seem to find any difference between applying a ViewModifier using either .modifier or .layout. They both produce the same result. Anyone knows what's the difference between these two. There's no documentation whatsoever.
For example, given this modifier:
struct RedTitle: ViewModifier {
func body(content: Content) -> some View {
return content.foregroundColor(.red).font(.title)
}
}
These two views turn out to look identical:
Text("Hello world!").layout(RedTitle())
Text("Hello world!").modifier(RedTitle())
UPDATE
As of Xcode 11 beta 4, the layout modifier has been marked deprecated:
extension View {
#available(*, deprecated, renamed: "modifier")
#inlinable public func layout<T>(_ layout: T) -> some SwiftUI.View where T : SwiftUI.ViewModifier {
return modifier(layout)
}
}
ORIGINAL
There is no difference as of Xcode 11 beta 2. That doesn't mean there will always be no difference. Possibly layout is left over from an older design and needs to be removed, or perhaps a later beta will make it behave differently.
The complete interface exported by SwiftUI can be found in this file:
/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64.swiftinterface
Looking in that file, you can find the declaration of func modifier:
extension View {
public typealias Modified<T> = _ModifiedContent<Self, T> where T : SwiftUI.ViewModifier
#inlinable public func modifier<T>(_ modifier: T) -> Modified<T> where T : SwiftUI.ViewModifier {
return .init(content: self, modifier: modifier)
}
}
And the declaration of func layout:
extension View {
#inlinable public func layout<T>(_ layout: T) -> Modified<T> where T : SwiftUI.ViewModifier {
return modifier(layout)
}
}
Because both modifier and layout are declared #inlinable, Swift includes the function bodies in the .swiftinterface file. We can see that layout just calls modifier and does nothing else.