
This video is only available to subscribers. Start a subscription today to get access to this and 470 other videos.
My Podcasts Screen
This episode is part of a series: Making a Podcast App From Scratch.
Extracting Common Logic of Searching Podcast
We will start by extracting the common code to display each podcast along with the details in the PodcastCell
. This will be the base class for our search results and the My Podcasts screen.
class PodcastCell : UITableViewCell {
@IBOutlet weak var artworkImageView: UIImageView!
@IBOutlet weak var podcastTitleLabel: UILabel!
@IBOutlet weak var podcastAuthorLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
artworkImageView.backgroundColor = Theme.Colors.gray3
artworkImageView.layer.cornerRadius = 10
artworkImageView.layer.masksToBounds = true
backgroundColor = Theme.Colors.gray4
backgroundView = UIView()
backgroundView?.backgroundColor = Theme.Colors.gray4
selectedBackgroundView = UIView()
selectedBackgroundView?.backgroundColor = Theme.Colors.gray3
podcastTitleLabel.textColor = Theme.Colors.gray0
podcastAuthorLabel.textColor = Theme.Colors.gray1
}
}
To configure the podcast with its details, we will create a protocol with podcast details and assign it as a model for the configuration.
protocol PodcastCellModel {
var titleText: String? { get }
var authorText: String? { get }
var artwork: URL? { get }
}
func configure(with model: PodcastCellModel) {
podcastTitleLabel.text = model.titleText
podcastAuthorLabel.text = model.authorText
if let url = model.artwork {
let options: KingfisherOptionsInfo = [
.transition(.fade(0.5))
]
artworkImageView.kf.setImage(with: url, options: options)
}
}
We will now conform the SeachResultsCell
to the protocol PodcastCell
.
class SearchResultCell : PodcastCell {
}
This way we can add additional behaviors to our SearchResultCell
along with the existing and shared behaviors.
Fetching the Subscriptions
Now we will create an extension of PodcastEntity
to conform the PodcastCellModel
.
extension PodcastEntity : PodcastCellModel {
var titleText: String? { return title }
var authorText: String? { return author }
var artwork: URL? { return artworkURL }
}
Next, we will create a shared view controller. This will act as a superclass for other controllers and will contain the shared logic of the table view used for the display of the My Podcasts screen and search results.
class PodcastListTableViewController : UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorInset = .zero
tableView.backgroundColor = Theme.Colors.gray4
tableView.separatorColor = Theme.Colors.gray3
}
func showPodcast(with lookupInfo: PodcastLookupInfo) {
let detailVC = UIStoryboard(name: "PodcastDetail", bundle: nil).instantiateInitialViewController() as! PodcastDetailViewController
detailVC.podcastLookupInfo = lookupInfo
show(detailVC, sender: self)
}
}
We will then create a storyboard for My Podcasts
. The user interface will be similar to the search results.
In SubscriptionStore
we will now fetch all the active subscriptions and the details related to the podcast from the database.
We will also indicate that we want to prefetch the podcast
relationship. This will fetch a subscription along with all the related podcast objects in a single fetch query. We also indicate that we want to sort the results with the newest subscription on top.
func fetchSubscriptions() throws -> [SubscriptionEntity] {
let fetch: NSFetchRequest<SubscriptionEntity> = SubscriptionEntity.fetchRequest()
fetch.returnsObjectsAsFaults = false
fetch.relationshipKeyPathsForPrefetching = ["podcast"]
fetch.sortDescriptors = [NSSortDescriptor(key: "dateSubscribed", ascending: false)]
return try mainContext.fetch(fetch)
}
On our view controller we will fetch the active subscriptions in viewDidLoad
. As we fetch the subscriptions and update it, we will also update the podcasts. Sometimes a podcast may get deleted but the link to subscription stays, to avoid such kind of nil
podcast, we will filter out this data before we load the subscriptions.
private let store: SubscriptionStore = .shared
private var subscriptions: [SubscriptionEntity] = [] {
didSet {
podcasts = subscriptions.compactMap { $0.podcast }
}
}
private var podcasts: [PodcastEntity] = []
private var subscriptionChangedObserver: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
loadSubscriptions()
subscriptionChangedObserver = NotificationCenter.default
.addObserver(SubscriptionsChanged.self,
sender: nil,
queue: .main,
using: { change in
self.loadSubscriptions()
})
}
private func loadSubscriptions() {
do {
subscriptions = try store.fetchSubscriptions()
tableView.reloadData()
} catch {
print("ERROR: ", error.localizedDescription)
}
}
Next we can configure our table view to display details about the podcasts we've subscribed to. When the user selects a row, we create a lookup info and use the method from the base class to show the podcast. We do this to make sure we fetch the latest info from the podcast as well as all of the latest episodes.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return podcasts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PodcastCell = tableView.dequeueReusableCell(for: indexPath)
let podcast = podcasts[indexPath.row]
cell.configure(with: podcast)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let podcast = podcasts[indexPath.row]
let lookup = PodcastLookupInfo(id: podcast.id!, feedURL: URL(string: podcast.feedURLString!)!)
showPodcast(with: lookup)
}
Sending Notification Using TypeNotification
There are two primary ways we can update this screen when users subscribe or unsubscribe from podcasts:
1) One is to fetch the data every time the screen is loaded.
2) Another way is to bind to these events in the background by monitoring the changed data while loading and reloading.
To load the changed details of subscribed and unsubscribed podcast, we will create a struct that conforms to TypedNotification
and set Ids of subscribed and unsubscribed podcast.
struct SubscriptionsChanged : TypedNotification {
var sender: Any?
static var name: String = "SubscriptionsChangedNotification"
let subscribedIds: Set<String>
let unsubscribedIds: Set<String>
init(subscribed: Set<String>) {
subscribedIds = subscribed
unsubscribedIds = []
}
init(unsubscribed: Set<String>) {
subscribedIds = []
unsubscribedIds = unsubscribed
}
}
Next, to know the changes in subscriptions, we will post the notification with podcast details in SubscriptionStore
.
let change = SubscriptionsChanged(subscribed: [podcast.id])
NotificationCenter.default.post(change)
let change = SubscriptionsChanged(unsubscribed: [podcast.id])
NotificationCenter.default.post(change)
Next, we will observe the TypeNotification
in MyPodcastsViewController
.
subscriptionChangedObserver = NotificationCenter.default
.addObserver(SubscriptionsChanged.self,
sender: nil,
queue: .main,
using: { change in
self.loadSubscriptions()
})