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
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.
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)
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
}
}
}
This question, in the old program that is implemented under UIKit, was done like this.
Condition in which the following actions are performed:
if(theApp().m_Disp.Connecttosrv(self.SelCon)) {
In the condition, the function is accessed
func theApp() -> iSPultApp!
{
return UIApplication.shared as? iSPultApp
}
Where next the class is called from the function
class iSPultApp: UIApplication {
var m_Disp: Chroot = Chroot()
Everything works there, is it possible to redo it somehow for SwiftUI?
The program goes to func theApp (), and then instead of going to the class, returns to the condition and there is an error:
if(theApp().m_Disp.Connecttosrv(self.SelCon)) {
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value.
self.SelCon is not empty, but filled with data. apparently, nil is passed in func theApp()
Thank you in advance for your help 🙏
I have four obscure questions about the usual process of boxing weak references.
To demonstrate the issues, here's a notification system, Notes.
You'd use it like this...
class SyncedCell: UITableViewCell, Notes {
override func layoutSubviews() {
...
listen(forNote: "soloCell")
}
func editing() {
say(note: "soloCell")
...
input.becomeFirstResponder()
}
func note() {
print("Hooray, I got a note..")
editingCancel()
}
So, the code for Notes is below.
For a given key (say "soloCell") you simply keep an array of references to any object which wants to get a message when that key is called.
Naturally, these have to be weak references.
So, when a new object arrives that needs to be memorized in the list...
var b:_Box = _Box()
b.p = self
_notes[k]?.append(b)
(The "listen" function just adds that object to the list of items for that key. The "say" function runs through the list of listening items for that key: for each item - if the item has not gone away in the meantime - it sends a message.)
So! As far as I know, you cannot keep an array of weak references.
You have to box them up, as you see in the kodes below.
Really, is that correct? Using a box is ugly, is there a way to just plain keep a list of weak references? Without having to box?
Relatedly: in the kodes AnyObject is the base. Is that best?
Notice the protocol is not : class. This is disturbing and I'm not sure if it should be.
Note that very unfortunately in Swift - as far as I know - you cannot observe a weak reference going to nil. Is this still the case as of 2017? Is there any way at all to achieve this?
Footnote - regarding point 4, "is there any way to achieve this?" The only possibility seems to be to add an associatedPbject to the watched items. (Example of that)[https://stackoverflow.com/a/32607010/294884]
code...
struct _Box {
weak var p: AnyObject?
// note: I prefer to spell out the assigment,
// rather than have a convenience initializer here
}
var _notes:[String:[_Box]] = [:]
protocol Notes {
func note()
}
extension Notes where Self:AnyObject {
func listen(forNote k: String) {
if _notes.index(forKey: k) == nil {
_notes[k] = []
}
var b:_Box = _Box()
b.p = self
_notes[k]?.append(b)
}
func say(note k:String) {
if let _n = _notes[k] {
var k:Int = 0
print("notes.4 saying......")
for b in _n {
let p = b.p
if (p == nil) {
print("\(k) notes.4 there's one that's been removed")
}
else {
print("\(k) notes.4 sending ok...")
(p as! Notes).note()
}
k = k + 1
}
}
__noteCleaner()
}
func __noteCleaner() {
for var (k, _n) in _notes {
let kn = _n.count
for i in (0..<kn).reversed() {
let p = _n[i].p
if (p == nil) {
_n.remove(at: i)
let newk = _n.count
print("notes.4, removed a dud listener for key \(k) new length is \(newk)")
}
}
if (_n.count == 0) {
print("notes.4, removed a seemingly unused key \(k)")
_notes.removeValue(forKey: k)
}
}
}
}
I will concentrate on the heart of the problem.
You are trying to implement observer (listener) pattern by registering the observers on the subject. I guess the subject will then manually call every observer.
There is a simpler way to implement this. You can use a singleton. The observers will register at the singleton and the subject will notify the singleton when something important is happening.
There is special class for this, NotificationCenter. And it will keep only unowned references.
Delegates are not made for one observer, not for multiple observers.
Of course, there is a simple way to implement an array of weak references by wrapping the reference into a struct/object, see How do I declare an array of weak references in Swift? . I guess that's what your Box is doing?
However, I somehow think that your problem is caused by your architecture. You are mixing your view classes with your model classes and that's not a good idea.
To answer specifically your questions:
No. I believe the discussion about this in the Swift mailing list ended with "you can wrap it". You don't have to wrap it so simply. You can write your own version of array that will do the wrapping internally.
Use a generic instead of AnyObject, see the linked question above.
Yes this is disturbing. This is possible because you actually only use it on AnyObject (that is, classes) and you are removing the types in Box. With Box.P declared as Note this wouldn't work unless Note were a class protocol.
This is correct. See Know when a weak var becomes nil in Swift?. A simple workaround is to remove the listener manually when the listener is being deallocated (in deinit).