iOS 14 Widget not update - ios

I'm trying to build an IOS 14 Widget, that updates every two minutes. This is the widget code:
import WidgetKit
import SwiftUI
import Intents
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
}
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let entry1 = SimpleEntry(date: Date(), configuration: configuration)
let timeline = Timeline(entries: [entry1], policy: .after(Date().addingTimeInterval(2*60.0)))
print("timeline: \(timeline)")
completion(timeline)
}
}
struct TeamWidgetEntryView : View {
#Environment(\.widgetFamily) var family
var entry: Provider.Entry
#ViewBuilder
var body: some View {
switch family {
case .systemSmall:
SmallView(date: Date())
case .systemMedium:
MediumView()
case .systemLarge:
LargeView()
default:
Text("Some other WidgetFamily in the future.")
}
}
}
#main
struct TeamWidget: Widget {
let kind: String = "TeamWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
TeamWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall,.systemMedium,.systemLarge])
}
}
This is the SmallView class:
struct SmallView: View {
var date: Date
static let taskDateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.timeStyle = .medium
return formatter
}()
var body: some View {
VStack {
Text("Small View")
Text("\(Date(), formatter: Self.taskDateFormat)")
}
}
}
I want that the Widget will update every 2 minutes but it's not happening, any idea what is the problem?

I was also facing the same problem and tried every update time from 10 seconds to 10 minutes and it seems that it starts updating the content only from 5 minutes

this is what I use to update my widget every minute
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
var entries = [SimpleEntry]()
let currentDate = Date()
let midnight = Calendar.current.startOfDay(for: currentDate)
let nextMidnight = Calendar.current.date(byAdding: .day, value: 1, to: midnight)!
for offset in 0 ..< 60 * 24 {
let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: midnight)!
entries.append(SimpleEntry(date: entryDate))
}
let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
completion(timeline)
}

Related

Widget with relative date text not updating when device is locked

I have a widget with a SwiftUI Text timer date. It countdowns as expected. However, when the device is locked, the timer is frozen.
To reproduce it, add the SwiftUI Text(_:style:) view to a widget and place the widget on the "Today View". The countdown should work as expected. However, lock the phone then view the Today View in a locked state. The timer is frozen.
Below is the full working sample code:
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}
struct TestWidget123EntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .timer)
.multilineTextAlignment(.center)
.padding()
}
}
#main
struct TestWidget123: Widget {
let kind: String = "TestWidget123"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
TestWidget123EntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct TestWidget123_Previews: PreviewProvider {
static var previews: some View {
TestWidget123EntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
I don't have any data protected data and I tried using a timer but didn't work at all. How can I allow the Text timer to countdown in a locked state?

Error while running widgetkit extension target on physical device

Added a WidgetBuilder to my extension so I could include a second type of widget for my app. I keep getting this error in a popup when building it onto my device.
Details
SendProcessControlEvent:toPid: encountered an error: Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8 "Failed to show Widget 'com.identifier' error: Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=2 "Please specify the widget kind in the scheme's Environment Variables using the key '_XCWidgetKind' to be one of: 'QuoteWidget', 'RandomWidget'" UserInfo={NSLocalizedDescription=Please specify the widget kind in the scheme's Environment Variables using the key '_XCWidgetKind' to be one of: 'QuoteWidget', 'RandomWidget'}." UserInfo={NSLocalizedDescription=Failed to show Widget 'com.identifiert' error: Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=2 "Please specify the widget kind in the scheme's Environment Variables using the key '_XCWidgetKind' to be one of: 'QuoteWidget', 'RandomWidget'" UserInfo={NSLocalizedDescription=Please specify the widget kind in the scheme's Environment Variables using the key '_XCWidgetKind' to be one of: 'QuoteWidget', 'RandomWidget'}., NSUnderlyingError=0x12f915290 {Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=2 "Please specify the widget kind in the scheme's Environment Variables using the key '_XCWidgetKind' to be one of: 'QuoteWidget', 'RandomWidget'" UserInfo={NSLocalizedDescription=Please specify the widget kind in the scheme's Environment Variables using the key '_XCWidgetKind' to be one of: 'QuoteWidget', 'RandomWidget'}}}
Domain: DTXMessage
Code: 1
--
Heres the before and after code.
Before adding the second widget:
import WidgetKit
import SwiftUI
import Foundation
let testBook = Book(id: UUID(), name: "Name", author: "Author", genre: "Error", page: "0", total: "77")
public enum AppGroup: String {
case Livre = "group.com.idetifier"
public var containerURL: URL {
switch self {
case .Livre:
return FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: self.rawValue)!
}
}
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), book: testBook)
}
func getSnapshot(in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), book: testBook)
completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)!
let books: [Book] = self.load("list")
let entry = SimpleEntry(date: currentDate, book: books.randomElement() ?? testBook)
let timeline = Timeline(entries: [entry], policy: .after(entryDate))
completion(timeline)
}
func load<T: Decodable>(_ filename: String) -> T {
let groupDirectory = AppGroup.Livre.containerURL
let groupURL = groupDirectory
.appendingPathComponent(filename)
.appendingPathExtension("json")
return try! JSONDecoder().decode(T.self, from: Data(contentsOf: groupURL))
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let book: Book
}
struct PlaceholderView: View {
var body: some View {
RandomWidgetView(book: testBook)
.redacted(reason: .placeholder)
}
}
struct LivreWidgetEntryView : View {
#Environment(\.widgetFamily) var family
var entry: Provider.Entry
#ViewBuilder
var body: some View {
switch family {
case .systemSmall:
RandomWidgetView(book: entry.book, size: .small)
case .systemMedium:
RandomWidgetView(book: entry.book, size: .medium)
case .systemLarge:
RandomWidgetView(book: entry.book, size: .large)
default:
RandomWidgetView(book: entry.book, size: .small)
}
}
}
#main
struct LivreWidget: Widget {
let kind: String = "LivreWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
LivreWidgetEntryView(entry: entry)
}
.configurationDisplayName("Progress of a random book")
.description("This widget shows the progress from a random book in your Library")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
and after adding the second widget:
import WidgetKit
import SwiftUI
import Foundation
#main
struct LivreWidgetBuilder: WidgetBundle {
init() {}
#WidgetBundleBuilder
var body: some Widget {
RandomWidget()
QuoteWidget()
}
}
let testBook = Book(id: UUID(), name: "Name", author: "Author", genre: "Error", page: "0", total: "77", quotes: ["Quote"])
public enum AppGroup: String {
case Livre = "group.com.identifier"
public var containerURL: URL {
switch self {
case .Livre:
return FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: self.rawValue)!
}
}
}
//MARK: Random book widget
struct RandomProvider: TimelineProvider {
func placeholder(in context: Context) -> RandomSimpleEntry {
RandomSimpleEntry(date: Date(), book: testBook)
}
func getSnapshot(in context: Context, completion: #escaping (RandomSimpleEntry) -> ()) {
let entry = RandomSimpleEntry(date: Date(), book: testBook)
completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)!
let books: [Book] = self.load("list")
let entry = RandomSimpleEntry(date: currentDate, book: books.randomElement() ?? testBook)
let timeline = Timeline(entries: [entry], policy: .after(entryDate))
completion(timeline)
}
func load<T: Decodable>(_ filename: String) -> T {
let groupDirectory = AppGroup.Livre.containerURL
let groupURL = groupDirectory
.appendingPathComponent(filename)
.appendingPathExtension("json")
return try! JSONDecoder().decode(T.self, from: Data(contentsOf: groupURL))
}
}
struct RandomSimpleEntry: TimelineEntry {
let date: Date
let book: Book
}
struct RandomPlaceholderView: View {
var body: some View {
RandomWidgetView(book: testBook)
.redacted(reason: .placeholder)
}
}
struct RandomWidgetEntryView : View {
#Environment(\.widgetFamily) var family
var entry: RandomProvider.Entry
#ViewBuilder
var body: some View {
switch family {
case .systemSmall:
RandomWidgetView(book: entry.book, size: .small)
case .systemMedium:
RandomWidgetView(book: entry.book, size: .medium)
case .systemLarge:
RandomWidgetView(book: entry.book, size: .large)
default:
RandomWidgetView(book: entry.book, size: .small)
}
}
}
struct RandomWidget: Widget {
let kind: String = "RandomWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: RandomProvider()) { entry in
RandomWidgetEntryView(entry: entry)
}
.configurationDisplayName("Random book progress")
.description("This widget shows the progress from a random book in your Library")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
//MARK: Quote Widget
struct QuoteProvider: TimelineProvider {
func placeholder(in context: Context) -> QuoteSimpleEntry {
QuoteSimpleEntry(date: Date(), book: testBook)
}
func getSnapshot(in context: Context, completion: #escaping (QuoteSimpleEntry) -> ()) {
let entry = QuoteSimpleEntry(date: Date(), book: testBook)
completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)!
let books: [Book] = self.load("list")
let entry = QuoteSimpleEntry(date: currentDate, book: books.randomElement() ?? testBook)
let timeline = Timeline(entries: [entry], policy: .after(entryDate))
completion(timeline)
}
func load<T: Decodable>(_ filename: String) -> T {
let groupDirectory = AppGroup.Livre.containerURL
let groupURL = groupDirectory
.appendingPathComponent(filename)
.appendingPathExtension("json")
return try! JSONDecoder().decode(T.self, from: Data(contentsOf: groupURL))
}
}
struct QuoteSimpleEntry: TimelineEntry {
let date: Date
let book: Book
}
struct QuotePlaceholderView: View {
var body: some View {
QuoteWidgetView(book: testBook)
.redacted(reason: .placeholder)
}
}
struct QuoteWidgetEntryView : View {
#Environment(\.widgetFamily) var family
var entry: QuoteProvider.Entry
var body: some View {
QuoteWidgetView(book: entry.book, size: .small)
}
}
struct QuoteWidget: Widget {
let kind: String = "QuoteWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: QuoteProvider()) { entry in
QuoteWidgetEntryView(entry: entry)
}
.configurationDisplayName("Quote widget")
.description("This widget shows a random quote from a random book in your Library")
.supportedFamilies([.systemMedium])
}
}
The widgets seem to work, but I'm trying to find out why i'm getting this popup error.

Pass data entered in a UIView to a Widget

I try to implement a simple pattern on Widgets:
get a textField in the app;
have the Widget updated when textField is changed.
App is UIKit (not SwiftUI).
I've read here that I could pass it through UserDefaults,
How to pass uiviewcontroller data to swiftui widget class
and also through a shared singleton. I tried but couldn't make it work.
What I tried:
Create a singleton to hold the data to pass:
class Util {
class var shared : Util {
struct Singleton {
static let instance = Util()
}
return Singleton.instance;
}
var globalToPass = "Hello"
}
shared the file between the 2 targets App and WidgetExtension
In VC, update the singleton when textField is changed and ask for widget to reload timeline
#IBAction func updateMessage(_ sender: UITextField) {
Util.shared.globalToPass = valueToPassLabel.text ?? "--"
WidgetCenter.shared.reloadTimelines(ofKind: "WidgetForTest")
WidgetCenter.shared.reloadAllTimelines()
}
Problem : Widget never updated its message field
Here is the full widget code at this time:
import WidgetKit
import SwiftUI
struct LoadStatusProvider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), loadEntry: 0, message: Util.shared.globalToPass)
}
func getSnapshot(in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), loadEntry: 0, message: Util.shared.globalToPass)
completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for minuteOffset in 0 ..< 2 {
let entryDate = Calendar.current.date(byAdding: .minute, value: 5*minuteOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, loadEntry: minuteOffset, message: Util.shared.globalToPass)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let loadEntry: Int
let message: String
}
struct WidgetForTestNoIntentEntryView : View {
var entry: LoadStatusProvider.Entry
var body: some View {
let formatter = DateFormatter()
formatter.timeStyle = .medium
let dateString = formatter.string(from: entry.date)
return
VStack {
Text(String(entry.message))
HStack {
Text("Started")
Text(entry.date, style: .time)
}
HStack {
Text("Now")
Text(dateString)
}
HStack {
Text("Loaded")
Text(String(entry.loadEntry))
}
}
}
}
#main
struct WidgetForTestNoIntent: Widget {
let kind: String = "WidgetForTestNoIntent"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: LoadStatusProvider()) { entry in
WidgetForTestNoIntentEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct WidgetForTestNoIntent_Previews: PreviewProvider {
static var previews: some View {
WidgetForTestNoIntentEntryView(entry: SimpleEntry(date: Date(), loadEntry: 0, message: "-"))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}

How to ensure WidgetKit view shows correct results from #FetchRequest?

I have an app that uses Core Data with CloudKit. Changes are synced between devices. The main target has Background Modes capability with checked Remote notifications. Main target and widget target both have the same App Group, and both have iCloud capability with Services set to CloudKit and same container in Containers checked.
My goal is to display actual Core Data entries in SwiftUI WidgetKit view.
My widget target file:
import WidgetKit
import SwiftUI
import CoreData
// MARK: For Core Data
public extension URL {
/// Returns a URL for the given app group and database pointing to the sqlite database.
static func storeURL(for appGroup: String, databaseName: String) -> URL {
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
fatalError("Shared file container could not be created.")
}
return fileContainer.appendingPathComponent("\(databaseName).sqlite")
}
}
var managedObjectContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
var workingContext: NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = managedObjectContext
return context
}
var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Countdowns")
let storeURL = URL.storeURL(for: "group.app-group-countdowns", databaseName: "Countdowns")
let description = NSPersistentStoreDescription(url: storeURL)
container.loadPersistentStores(completionHandler: { storeDescription, error in
if let error = error as NSError? {
print(error)
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
return container
}()
// MARK: For Widget
struct Provider: TimelineProvider {
var moc = managedObjectContext
init(context : NSManagedObjectContext) {
self.moc = context
}
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
return completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}
struct CountdownsWidgetEntryView : View {
var entry: Provider.Entry
#FetchRequest(entity: Countdown.entity(), sortDescriptors: []) var countdowns: FetchedResults<Countdown>
var body: some View {
return (
VStack {
ForEach(countdowns, id: \.self) { (memoryItem: Countdown) in
Text(memoryItem.title ?? "Default title")
}.environment(\.managedObjectContext, managedObjectContext)
Text(entry.date, style: .time)
}
)
}
}
#main
struct CountdownsWidget: Widget {
let kind: String = "CountdownsWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(context: managedObjectContext)) { entry in
CountdownsWidgetEntryView(entry: entry)
.environment(\.managedObjectContext, managedObjectContext)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct CountdownsWidget_Previews: PreviewProvider {
static var previews: some View {
CountdownsWidgetEntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
But I have a problem: let's say I have 3 Countdown records in the main app:
At the start widget view shows 3 records as expected in preview (UI for adding a widget). But after I add a widget to the home screen, it does not show Countdown rows, only entry.date, style: .time. When timeline entry changes, rows not visible, too. I made a picture to illustrate this better:
Or:
At the start widget view shows 3 records as expected, but after a minute or so, if I delete or add Countdown records in the main app, widget still shows initial 3 values, but I want it to show the actual number of values (to reflect changes). Timeline entry.date, style .time changes, reflected in the widget, but not entries from request.
Is there any way to ensure my widget shows correct fetch request results? Thanks.
Widget views don't observe anything. They're just provided with TimelineEntry data. Which means #FetchRequest, #ObservedObject etc. will not work here.
Enable remote notifications for your container:
let container = NSPersistentContainer(name: "DataModel")
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
Update your CoreDataManager to observe remote notifications:
class CoreDataManager {
var itemCount: Int?
private var observers = [NSObjectProtocol]()
init() {
fetchData()
observers.append(
NotificationCenter.default.addObserver(forName: .NSPersistentStoreRemoteChange, object: nil, queue: .main) { _ in
// make sure you don't call this too often - notifications may be posted in very short time frames
self.fetchData()
}
)
}
deinit {
observers.forEach(NotificationCenter.default.removeObserver)
}
func fetchData() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
do {
self.itemCount = try CoreDataStack.shared.managedObjectContext.count(for: fetchRequest)
WidgetCenter.shared.reloadAllTimelines()
} catch {
print("Failed to fetch: \(error)")
}
}
}
Add another field in the Entry:
struct SimpleEntry: TimelineEntry {
let date: Date
let itemCount: Int?
}
Use it all in the Provider:
struct Provider: TimelineProvider {
let coreDataManager = CoreDataManager()
...
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> Void) {
let entries = [
SimpleEntry(date: Date(), itemCount: coreDataManager.itemCount),
]
let timeline = Timeline(entries: entries, policy: .never)
completion(timeline)
}
}
Now you can display your entry in the view:
struct WidgetExtEntryView: View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.date, style: .time)
Text("Count: \(String(describing: entry.itemCount))")
}
}
}

RSS Widget For iOS 14 Shows Empty

I use an XMLParser for my app written in Swift, and am trying to get a Widget set up for iOS 14 in which it will show the most recent article on the RSS. I see the placeholder just fine when I add it, but it stays empty with just a line where text should be and never seems to get the information from the timeline.
struct Provider: TimelineProvider {
#State private var rssItems:[RSSItem]?
let feedParser = FeedParser()
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), title:"News", description: "News article here", link: "Http://link", pubDate: "The day it posted")
}
func getSnapshot(in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), title:"News", description: "News Article Here", link: "Http://link", pubDate: "The day it posted")
completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<SimpleEntry>) -> ()) {
var entries: [SimpleEntry] = []
feedParser.parseFeed(url: "http://fritchcoc.wordpress.com/feed") {(rssItems) in
self.rssItems = rssItems
let currentDate = Date()
let entry = SimpleEntry(date: currentDate, title:rssItems[0].title, description: rssItems[0].description, link: rssItems[0].link, pubDate: rssItems[0].pubDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let title: String
let description: String
let link: String
let pubDate: String
}
struct FritchNewsEntryView : View {
var entry: SimpleEntry
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(entry.title)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .leading)
.padding()
.background(LinearGradient(gradient: Gradient(colors: [.orange, .yellow]), startPoint: .top, endPoint: .bottom))
}
}
#main
struct FritchNews: Widget {
let kind: String = "FritchNews"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
FritchNewsEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
UPDATE BY THE OP:
It was indeed a combination of the timeline being set in the wrong spot, along with a couple of typos within the parser that caused this particular issue.
Apple does not allow HTTP connections by default, the easiest solution is to change the URL to https://fritchcoc.wordpress.com/feed
If you want to allow HTTP connections in your app (or widget) you can add exceptions (to one domain or all) in the Info.plist for the target, see NSAppTransportSecurity at Apple Docs
Edit:
After a better look and finding for the implementation of FeedParser online, I noticed that parseFeed() is asynchronous.
Thus the completion is called with an empty array, it should be called after the parsing is done:
struct Provider: TimelineProvider {
...
func getTimeline(in context: Context, completion: #escaping (Timeline<SimpleEntry>) -> ()) {
var entries: [SimpleEntry] = []
feedParser.parseFeed(url: "http://fritchcoc.wordpress.com/feed") {(rssItems) in
self.rssItems = rssItems
let currentDate = Date()
let entry = SimpleEntry(date: currentDate, title:rssItems[0].title, description: rssItems[0].description, link: rssItems[0].link, pubDate: rssItems[0].pubDate)
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
}

Resources