<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>iOS Home Screen » MszPro・株式会社Smartソフト</title>
	<atom:link href="https://mszpro.com/category/ios-home-screen/feed" rel="self" type="application/rss+xml" />
	<link>https://mszpro.com</link>
	<description>iOS VisionOS SwiftUI Programming Blog. Dream it, Chase it, Code it.</description>
	<lastBuildDate>Mon, 16 Dec 2024 12:37:10 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>

<image>
	<url>https://static-assets.mszpro.com/2024/12/cropped-Unknown-32x32.webp</url>
	<title>iOS Home Screen » MszPro・株式会社Smartソフト</title>
	<link>https://mszpro.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Making a user-configurable widget for your iOS app</title>
		<link>https://mszpro.com/configurable-widget</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 07:52:07 +0000</pubDate>
				<category><![CDATA[iOS Home Screen]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=389</guid>

					<description><![CDATA[<p>In this article, we&#8217;ll cover the following: You can check out the completed source code here. Adding Widgets to an Existing Application Creating the Application Target Adding a widget to an existing iOS application is simple. Add the target Widget Extension. Now, make sure you have checked the Include Configuration Intent box. We&#8217;ll need that [&#8230;]</p>
<p>The post <a href="https://mszpro.com/configurable-widget">Making a user-configurable widget for your iOS app</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="320" height="693" src="https://static-assets.mszpro.com/2024/12/https-qiita-image-store.s3.ap-northeast-1.amazonaws.com-0-635330-1b643c98-60e5-3195-d0b1-e03a027de0c0.gif" alt="" class="wp-image-390"/></figure>



<p><strong>In this article, we&#8217;ll cover the following:</strong></p>



<ol class="wp-block-list">
<li>Adding Widgets to an Existing Application</li>



<li>⭐️ Adding Configurable Widgets (e.g., user selects a city)</li>



<li>Reloading Widgets</li>
</ol>



<p><a href="https://github.com/mszmagic/Configurable-Widget_Example">You can check out the completed source code here.</a></p>



<h2 class="wp-block-heading">Adding Widgets to an Existing Application</h2>



<h3 class="wp-block-heading">Creating the Application Target</h3>



<p>Adding a widget to an existing iOS application is simple. Add the target <code>Widget Extension</code>.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/d81c538d-13fa-05b4-0957-3c152bc8fb61.png" alt="image"/></figure>



<p>Now, make sure you have checked the <code>Include Configuration Intent</code> box. We&#8217;ll need that configuration file in part 2 of this article.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/280a43e9-a6e5-8657-2625-e54898bb9461.png" alt="image"/></figure>



<h3 class="wp-block-heading">Data Structure</h3>



<p>You should now see a new folder named <code>WidgetExample-Widget</code>. Click to open the file <code>WidgetExample_Widget.swift</code>, delete its contents, and follow this guide.</p>



<p>You can create the data structure for the data you want to display in your widget. In this example, we&#8217;ll display information about a cat!</p>



<pre class="wp-block-code"><code>struct CatEntry: TimelineEntry {
    var date: Date
    
    var name: String
    var lastFed: Date
    var lastPlayedWith: Date
}
</code></pre>



<h3 class="wp-block-heading">Creating the <code>IntentTimelineProvider</code> Structure</h3>



<p>The <code>IntentTimelineProvider</code> structure provides three types of content:</p>



<ol class="wp-block-list">
<li><code>placeholder</code> is displayed while the widget is loading.</li>



<li><code>getSnapshot</code> is shown in the widget gallery.</li>



<li><code>getTimeline</code> is used for the actual widget display.</li>
</ol>



<p>First, create a struct conforming to the <code>IntentTimelineProvider</code> type, then define the type of <code>Entry</code>.</p>



<pre class="wp-block-code"><code>struct CatProviderStatic: TimelineProvider {
    typealias Entry = CatEntry

    func getSnapshot(in context: Context, completion: @escaping (CatEntry) -&gt; Void) {
        //TODO
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline&lt;CatEntry&gt;) -&gt; Void) {
        //TODO
    }

    func placeholder(in context: Context) -&gt; CatEntry {
        //TODO
    }
}
</code></pre>



<p>For the <code>getSnapshot</code> function, you can provide example values for the widget view to help users understand what information your widget will provide:</p>



<pre class="wp-block-code"><code>func getSnapshot(in context: Context, completion: @escaping (CatEntry) -&gt; Void) {
    let entry = CatEntry(date: Date(), name: "Cat Name", lastFed: Date(), lastPlayedWith: Date())
    completion(entry)
}
</code></pre>



<p>For the placeholder view, you can display empty or example values:</p>



<pre class="wp-block-code"><code>func placeholder(in context: Context) -&gt; CatEntry {
    return CatEntry(date: Date(), name: "Cat Name", lastFed: Date(), lastPlayedWith: Date())
}
</code></pre>



<p>For the timeline display, you can provide the actual content to be displayed.</p>



<p>In this example, we display static data values. In your application, you can fetch content from <code>Core Data</code> (see <a href="https://qiita.com/MaShunzhe/items/6d13422aee5dcfaf2cc2">this article</a> on how to share data), online, from <code>CloudKit</code>, or from <code>UserDefaults</code>.</p>



<pre class="wp-block-code"><code>func getTimeline(in context: Context, completion: @escaping (Timeline&lt;CatEntry&gt;) -&gt; Void) {
    let entry = CatEntry(date: Date(), name: "Neko No Hī", lastFed: Date(), lastPlayedWith: Date())
    let timeline = Timeline(entries: &#91;entry], policy: .atEnd)
    completion(timeline)
}
</code></pre>



<h4 class="wp-block-heading">Adding Multiple Items to the Timeline</h4>



<p>You can also add multiple items to the timeline. The widget will automatically check the timeline items and reload at the times indicated.</p>



<pre class="wp-block-code"><code>func getTimeline(in context: Context, completion: @escaping (Timeline&lt;CatEntry&gt;) -&gt; Void) {
    var timelineEntries = &#91;CatEntry]()
    if let date1 = Calendar.current.date(byAdding: .hour, value: 1, to: Date()) {
        let entry = CatEntry(date: date1, name: "Neko No Hī", lastFed: date1, lastPlayedWith: date1)
        timelineEntries.append(entry)
    }
    if let date2 = Calendar.current.date(byAdding: .hour, value: 2, to: Date()) {
        let entry = CatEntry(date: date2, name: "Neko No Hī", lastFed: date2, lastPlayedWith: date2)
        timelineEntries.append(entry)
    }
    let timeline = Timeline(entries: timelineEntries, policy: .atEnd)
    completion(timeline)
}
</code></pre>



<h3 class="wp-block-heading">Designing the Widget View</h3>



<p>Now, you can design the SwiftUI view for your widget.</p>



<pre class="wp-block-code"><code>struct CatWidgetView: View {
    
    @Environment(\.widgetFamily) var family
    
    var entry: CatEntry
    
    var body: some View {
        
        VStack {
            
            if family == .systemMedium || family == .systemLarge {
                Image("kitty")
                    .resizable()
                    .frame(width: 50, height: 50)
                    .padding(.vertical, 5)
            }
            
            Text(entry.name)
                .font(.headline)
                .padding(1)
            
            Text("Last played with at " + entry.lastPlayedWith.getString())
                .font(.caption)
                .padding(.horizontal)
            
            Text("Last fed at " + entry.lastFed.getString())
                .font(.caption)
                .padding(.horizontal)
            
        }
        
    }
}
</code></pre>



<p>You can use the <code>@Environment(\.widgetFamily) var family</code> variable to check the size of the widget.</p>



<p>In this example, we&#8217;re displaying a cat image if the widget is large enough to fit the image.</p>



<pre class="wp-block-code"><code>if family == .systemMedium || family == .systemLarge {
    Image("kitty")
        .resizable()
        .frame(width: 50, height: 50)
        .padding(.vertical, 5)
}
</code></pre>



<h3 class="wp-block-heading">Coding the Widget Application</h3>



<p>Now, you can code the widget application.</p>



<pre class="wp-block-code"><code>@main
struct CatWidget: Widget {
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: "CatWidget", intent: ConfigurationIntent.self, provider: CatProvider()) { entry in
            CatWidgetView(entry: entry)
        }.configurationDisplayName("Cat")
        .description("See when you last fed or played with your cat.")
    }
}
</code></pre>



<p>Now you can run the app on the simulator and add the widget you just designed to your screen.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/83eba8db-1e30-da2c-d9f0-2ed0cbeff687.jpeg" alt="image"/></figure>



<h2 class="wp-block-heading">Adding Configurable Widgets</h2>



<p>If you&#8217;ve used a weather widget, you&#8217;ll know that by long-pressing on the widget, users can configure it to display different cities. You can add this functionality using the <code>Intents</code> framework and Target.</p>



<h3 class="wp-block-heading">Adding the <code>Intents</code> Program Target</h3>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/aab04398-6109-2708-1590-9dac82cec707.png" alt="image"/></figure>



<p>At this stage, UI elements are unnecessary, so uncheck the <code>Include UI Extension</code> option.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/6da8d52d-1fab-87bf-33e6-eb0289a70527.png" alt="image"/></figure>



<p>In the created Intents target page, find the section named <code>Supported Intents</code>. Create a new item named <code>ConfigurationIntent</code>. Now, you can name this to anything, but make sure to be consistent and use the same name for the upcoming steps.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/ceb58192-71bc-cc58-2747-850a82100373.png" alt="スクリーンショット 0002-10-09 15.16.55.png"/></figure>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/c49dd939-7893-4d94-9070-82b15ba18f8c.png" alt="スクリーンショット 0002-10-09 15.11.01.png"/></figure>



<h3 class="wp-block-heading">Configuring <code>.intentdefinition</code></h3>



<p>Next, in</p>



<p>the previously created widget, add the newly created <code>Intents</code> extension as the intent target for the <code>WidgetExample_Widget.intentdefinition</code> file.</p>



<p>If you do not yet have an <code>intentdefinition</code> file, you can create one. Make sure to link the file to both the widget and the intent target.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/a9450674-1f3b-9d1d-f8c0-69221b74e7c2.png" alt="image"/></figure>



<p>Click <code>Configuration</code> on the left side of the screen. If no configurations exists, tap the plus mark and create a new intent, and modify the name to be <code>Configuration</code></p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/88b6539b-5891-275c-fb34-e4189715cb73.png" alt="image"/></figure>



<p>On the right side, ensure the configuration matches the following image.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/91f13c2f-f065-162f-19c4-cd9520f2a35d.png" alt="image"/></figure>



<p>Next, add a new parameter named <code>cat</code>.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/c6c7d415-0f7c-ad5d-3570-7eacb63b463a.png" alt="image"/></figure>



<p>In the settings screen for the newly created <code>cat</code> parameter, select <code>String</code> for the <code>Type</code> to use as the identifier for the cat.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/449616b7-e09d-f06c-9d1d-7386b92c24b4.png" alt="image"/></figure>



<h3 class="wp-block-heading">Configuring <code>IntentHandler</code></h3>



<p>Next, open the <code>IntentHandler.swift</code> file. This file provides options for users to configure the widget. In this example, the option will be the cat identifier.</p>



<p>Add the keyword <code>ConfigurationIntentHandling</code> next to the class type <code>INExtension</code> to allow Xcode to automatically display the next function names to add.</p>



<pre class="wp-block-code"><code>class IntentHandler: INExtension, ConfigurationIntentHandling {
    ...
}
</code></pre>



<p>In this example, the completed <code>IntentHandler.swift</code> file looks like this:</p>



<pre class="wp-block-code"><code>class IntentHandler: INExtension, ConfigurationIntentHandling {
    
    func provideCatOptionsCollection(for intent: ConfigurationIntent, searchTerm: String?, with completion: @escaping (INObjectCollection&lt;NSString&gt;?, Error?) -&gt; Void) {
        let catIdentifiers: &#91;NSString] = &#91;
            "Neko No Hī",
            "Mugi",
            "Azuki"
        ]
        let allCatIdentifiers = INObjectCollection(items: catIdentifiers)
        completion(allCatIdentifiers, nil)
    }
    
    override func handler(for intent: INIntent) -&gt; Any {
        // This is the default implementation. If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.
        return self
    }
}
</code></pre>



<p>In the <code>provideCatOptionsCollection</code> function, you need to input a list of values. These values can actually be fetched from <code>User Defaults</code>, <code>Core Data</code>, or online. In this example, the values are hard-coded.</p>



<p><a href="https://qiita.com/MaShunzhe/items/6d13422aee5dcfaf2cc2">Using <code>Core Data</code> with App Extensions</a></p>



<h3 class="wp-block-heading">Creating <code>IntentTimelineProvider</code></h3>



<p>In part 1 of this article, we used <code>TimelineProvider</code>. This time, we&#8217;ll use <code>IntentTimelineProvider</code>.</p>



<p>If you already have a regular timeline provider, you should replace all the function headers (parameters).</p>



<p>The data structures between <code>IntentTimelineProvider</code> and <code>TimelineProvider</code> are almost identical. The difference is that you&#8217;ll need to declare an additional <code>typealias</code>.</p>



<pre class="wp-block-code"><code>typealias Intent = ConfigurationIntent
</code></pre>



<p>Another difference is that each function receives an additional parameter representing the intent selection <code>ConfigurationIntent</code>.</p>



<pre class="wp-block-code"><code>struct CatProvider: IntentTimelineProvider {
    
    typealias Intent = ConfigurationIntent
    typealias Entry = CatEntry
    
    func placeholder(in context: Context) -&gt; CatEntry {
        let entry = CatEntry(date: Date(), name: "", lastFed: Date(), lastPlayedWith: Date())
        return entry
    }
    
    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (CatEntry) -&gt; Void) {
        let entry = CatEntry(date: Date(), name: "Cat Name", lastFed: Date(), lastPlayedWith: Date())
        completion(entry)
    }
    
    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline&lt;CatEntry&gt;) -&gt; Void) {
        let entry = CatEntry(date: Date(), name: configuration.cat ?? "", lastFed: Date(), lastPlayedWith: Date())
        let timeline = Timeline(entries: &#91;entry], policy: .atEnd)
        completion(timeline)
    }
}
</code></pre>



<p>You can use the <code>configuration.cat</code> property to read the value of the option selected by the user.</p>



<pre class="wp-block-code"><code>let entry = CatEntry(date: Date(), name: configuration.cat ?? "", lastFed: Date(), lastPlayedWith: Date())
</code></pre>



<h3 class="wp-block-heading">Updating <code>CatWidget</code> Code</h3>



<p>In part 1, we used <code>StaticConfiguration</code>. In this part, we&#8217;ll use <code>IntentConfiguration</code> (the name set in <code>Supported Intents</code>).</p>



<pre class="wp-block-code"><code>@main
struct CatWidget: Widget {
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: "CatWidget", intent: ConfigurationIntent.self, provider: CatProvider()) { entry in
            CatWidgetView(entry: entry)
        }.configurationDisplayName("Cat")
        .description("See when you last fed or played with your cat.")
    }
}
</code></pre>



<p>Now you can run the program on the simulator. By long-pressing the widget, you&#8217;ll see an option named <code>Edge Widget</code> and you can change the cat name.</p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/1b643c98-60e5-3195-d0b1-e03a027de0c0.gif" alt="ezgif-6-bf9e4b4783c9.gif"/></figure>



<h1 class="wp-block-heading">Reloading Widgets</h1>



<p>If content in the widget changes, you can manually call the reload function for the widget from the main iOS application.</p>



<pre class="wp-block-code"><code>// import WidgetKit
WidgetCenter.shared.reloadAllTimelines()
</code></pre>



<p>For example, if you have a ToDo app and a widget displaying the number of ToDo items, you can reload the widget when the user completes or adds a ToDo item.</p>



<p>:relaxed: <a href="https://twitter.com/MszPro">Twitter @MszPro</a></p>



<p>:sunny: <a href="https://mszpro.com/ioskiji/">Check out my list of publicly available Qiita articles by category.</a></p>



<figure class="wp-block-image"><img decoding="async" width="365" height="600" src="https://static-assets.mszpro.com/2024/12/AppClipImage_small.png" alt="" class="wp-image-391" srcset="https://static-assets.mszpro.com/2024/12/AppClipImage_small-183x300.png 183w, https://static-assets.mszpro.com/2024/12/AppClipImage_small.png 365w" sizes="(max-width: 365px) 100vw, 365px" /></figure><p>The post <a href="https://mszpro.com/configurable-widget">Making a user-configurable widget for your iOS app</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Database Caching using Disk (Request-wide modification query)

Served from: mszpro.com @ 2025-07-07 01:37:07 by W3 Total Cache
-->