#KpopTwitter is always one of the hottest topics on Twitter. No, it’s not just the Army. In 2019 alone, 6.1 billion Tweets were posted with the hashtag #KpopTwitter, from all over the world. I knew I had to join the movement in the most techy way imaginable: by building a K-pop iOS widget!
One of the new, and in my opinion, most exciting features of iOS 14, is the addition of widgets. Widgets can provide a glimpse into your favorite apps by previewing and updating interesting content throughout the day. They are gentle on battery consumption and make your home screen immediately more useful. If you want some inspiration, Clatters, Aviary, and Nighthawk are great examples of apps that have widgets that integrate with Twitter.
We’re going to create our own widget, and you can see the final result on our TwitterDev Github.
Widgets can display content that changes throughout the day. Throughout the day, our widget will display different images that Twitter recognizes to be in the context of K-pop. You don’t need to learn #KpopTwitter’s stan language; our app is going to rely on Tweet annotations to pick relevant images for you. Using Tweet annotations, the Twitter API v2 makes available Twitter’s interpretation of the context of a Tweet, as detected by our machine learning models. That’s right - you get machine learning right out of the box, with the Twitter API v2. What’s great about Tweet annotations is that you can use them with endpoints like recent search or filtered stream to get the Tweets you care about.
There are many annotations you can choose from, but for this app we’re going to use 55.888105153038958593 (Music Genre - K-pop) for our search query.
The widget extension specifies how often the content should change and refresh. Based on that, the OS decides what to display based on how much screen time a widget gets, the status of its parent app, and other circumstances. With that in mind, we’re going to display one image per hour. Just like the popular hit song "Just one day", using the code below, the widget will only request one day worth of Tweets through recent search:
// Compose the URL for Recent search
var components = URLComponents()
components.scheme = "https"
components.host = "api.twitter.com"
components.path = "/2/tweets/search/recent"
components.queryItems = [
// Request images
URLQueryItem(name: "tweet.fields", value: "attachments"),
// Request the profile picture of the user who Tweeted this image
URLQueryItem(name: "user.fields", value: "profile_image_url"),
// Request the url for each image in the Tweet
URLQueryItem(name: "media.fields", value: "url"),
// Include media and user object in the response
URLQueryItem(name: "expansions", value: "attachments.media_keys,author_id"),
// We're looking for K-pop Tweets with image, and no Retweets
URLQueryItem(name: "query", value: "context:55.888105153038958593 has:images -is:retweet"),
// 24 Tweets (so we can display one per hour)
URLQueryItem(name: "max_results", value: "24")
]
With the results, we’re going to create a Timeline object containing the 24 Tweets separated by hour:
let entries: [TweetEntry] = tweets.enumerated().compactMap {
(index, tweet) in
guard let media = response.media(tweet: tweet)?.first,
let user = response.user(of: tweet) else {
return nil
}
// Assign each Tweet to a specific hour of the day, based on the position in the payload
let date = Calendar.current.date(byAdding: .hour, value: index, to: date)!
return TweetEntry(date: date, media: media, tweet: tweet, user: user)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
Per the code above, the widget provides a Timeline object with 24 entries or fewer. WidgetKit will then present an entry based on the image’s date and time. It’s unlikely that we’ll ever run out of Tweets throughout the day, but policy: .atEnd will ensure that the widget creates a new Timeline (and consequently request new Tweets) once the OS goes through all the entries.
With the refresh logic ready, it’s time to build the UI for the home screen widget. SwiftUI makes it extremely easy to create the UI. In this case, we’re going to need only two components:
Lastly, here’s how we are creating the image to display:
struct TweetImage: View {
let media: Media?
@ViewBuilder
var body: some View {
if let mediaURLString = media?.url,
let mediaURL = URL(string: mediaURLString),
let imageData = try? Data(contentsOf: mediaURL),
let image = UIImage(data: imageData) {
Image(uiImage: image)
.resizable()
.scaledToFill()
} else {
VStack {
Text("🤳").font(.largeTitle)
Text("Loading pictures…").font(.caption)
}
}
}
}
Note how we’re displaying a placeholder message, if for any reason an image fails to upload (this can happen, for example, if a Tweet we requested earlier in the day is no longer available).
This is really all you need to create a widget! Check out the finished project on the TwitterDev Github to see the completed widget. Tweet at TwitterDev or my @ if you end up creating your own widget!