Apologies
You must be signed in to watch this lesson.
Podcasts Discussion and Bonus Lessons
Podcasts
If you have any suggest for bonus lessons, please leave them in the comments below for discussion. I currently have a few different ideas to add to this course but would love to hear from you as well. Thanks and I hope you've enjoyed this course up to this point.

Comments (27)
rinconchris
6 years ago
Handling landscape mode for the player would be interesting!
Aurélien Haie
6 years ago
Hi Brian, thanks for this course! I learned a lot by watching your videos and i will talk about your courses with all the developers i meet for sure. I would love to see a course about the Unit tests and the UI tests using the features from Apple included in the SDK XCTest class. Because I already had to use it in a project but I don't think I was using it right, I didn't have any idea of how to implement it so I used it in a very simple way. It would be nice to see the use of it in a professional way, all the things to consider when writing tests and a good way of writing and structuring the code. Maybe you've already made a course about it, I didn't see it.. Thanks!
Dave Gumba
6 years ago
Notifications!
syedfa
6 years ago
How to save and download episodes using Core Data instead of using UserDefaults.
humbleCoder
6 years ago
Or Realm! I hear a lot of companies using that nowadays.
irma
6 years ago
Hi Brian, great course, for bonus I would suggest some testing related to the project. Thanks.
Dew Douglass
5 years ago
Yes, how do we do testing professional style?
Dew Douglass
5 years ago
Thanks for the course Brian!
nicolehinckley
5 years ago
Thanks for the course Brian :)
forsuresh
5 years ago
Thank you for creating such a great course and helping me level up my iOS development skills., If you are still accepting ideas for bonus video or for your next course, I would really like an opportunity to see unit testing.
arian
5 years ago
Thank you for the course Brian.Can we have some service testing by capturing the service response and using mocked object.
arian
5 years ago
Please show us how we can have a protocol to implement different service testing functions .It would be great if you use associatedtype for the NetworkResponse return Type.
RichyCode
5 years ago
what would be really helpful I think is with in favourites, actually linking them back to the actual episode. Cant see this anywhere in the course
Paul Wen
5 years ago
After update swift version, when I addObserver (.AVAudioSession.interruptionNotification),(.AvAudioSession.routeChangeNotification) there are errors I can't fix . Help me please !
ZeroSingularity
5 years ago
Could you post the errors along with the code?
Brian Voong
5 years ago
Pavlos Nicolaou
5 years ago
A bonus lesson can be that we got auto download of the subscribed Podcasts with a notification of getting the download when a new Episode is live!
st4n
5 years ago
download circular animations in rows like in real app :)
ravikanth.marri@gmail.com
5 years ago
I would like to see unit tests , Because nowadays every company is stressing on TDD and Agile.
Brian Voong
5 years ago
ravikanth.marri@gmail.com
5 years ago
Cool , Thanks
ZiadHamdieh
5 years ago
Yes please, I would absolutely buy that course!!!
ravikanth.marri@gmail.com
5 years ago
One small bug on the websit , my comment is showing as added 2 months ago , But I just added the comment.
ravikanth.marri@gmail.com
5 years ago
Now this issue is fixed
Brian Voong
5 years ago
ravikanth.marri@gmail.com
5 years ago
Hi Brian, Its better you post your ideas and do a poll to pick one from them.
Luis Neria
5 years ago
Hi Brian, Great Course! Excellent job, thanks. I see requests for Unit Testing from other students, but that might be better addressed in a separate course. I have a couple of suggestion that would make the app more complete: 1. Handle suspend/resume of downloads for when the application exits or goes into background. 2. Adding download queue functionality to allow the user to initiate download of multiple episodes, or even download them concurrently. Best Regards, Luis
ZiadHamdieh
5 years ago
Ditto on this, ESPECIALLY adding download queue functionality to allow the user to initiate download of multiple episodes, or even download them concurrently.
Jake Liu
5 years ago
please add x2 play back speed for the course
cfva14
5 years ago
Please add how to handle playlists. So we can move on to Music Players
need2edit
5 years ago
Hi Brian, thank you for a great course. It would be really helpful to see a lesson on background notifications and how a download manager is reconstituted after the application has moved to the background etc. I've had a lot of trouble getting it right and would love to see your version of how to implement something like this. My apps have to download enormous files (several hundred mb or 1GB+) video files and usually in the background. Similar to that, I would love to see a lesson on AVAsset caching... this equates to offline playback of assets and AVAggregateAssetDownloadTask which have been a little elusive. https://developer.apple.com/documentation/avfoundation/avaggregateassetdownloadtask/ https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/using_avfoundation_to_play_and_persist_http_live_streams Thank you again for such great content!
nandesu
4 years ago
Hello Brian, Loved the course! I'm in the midst of trying to refactor the search and favorites functionality to be able to favorite only a specific episode, instead of the entire Podcast. I guess more along the lines of being able to create a playlist. I'm having a bit of a difficult time figuring out how to create the separate playlist view like your Episodes view, because it's so dependent on the did didSelectItemAt and the non transparent nature of saving via UserDefaults instead of core data or SQL. Do you have any plans in the near future to create a playlist like functionality, and or adding CoreData or SQL to replace the UserDefaults implementation? Thank you for your attention and time!
Hamza Shahbaz
4 years ago
Must return MPRemoteCommandHandlerStatus or take a completion handler as the second argument. Facing this issue after updating to xcode 11 and running on iOS 13 kindly help.
ZeroSingularity
4 years ago
Part of the solution is within the project itself, you just need to rearrange the order of the calls as well as expose some the commandCenter methods to MPRemoteCommandHandlerStatus. By enabling the commandCenter command after you add a target to it will silence the warnings. The only method I've been struggling with is the handleCurrentTimeSliderChange method to work after it has been exposed to MPRemoteCommandHandlerStatus. Here's an article that goes into minor detail about the issue: https://forums.developer.apple.com/thread/121540 Solution: fileprivate func setupRemoteControl() { UIApplication.shared.beginReceivingRemoteControlEvents() let commandCenter = MPRemoteCommandCenter.shared() commandCenter.playCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in self.player.play() self.playPauseButton.setImage(#imageLiteral(resourceName: "pause"), for: .normal) self.miniPlayPauseButton.setImage(#imageLiteral(resourceName: "pause"), for: .normal) self.setupElapsedTime(playbackRate: 1) return .success } commandCenter.playCommand.isEnabled = true commandCenter.pauseCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in self.player.pause() self.playPauseButton.setImage(#imageLiteral(resourceName: "play"), for: .normal) self.miniPlayPauseButton.setImage(#imageLiteral(resourceName: "play"), for: .normal) self.setupElapsedTime(playbackRate: 0) return .success } commandCenter.pauseCommand.isEnabled = true commandCenter.togglePlayPauseCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in self.handlePlayPause() return .success } commandCenter.togglePlayPauseCommand.isEnabled = true commandCenter.nextTrackCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in self.handleNextTrack() return .success } commandCenter.nextTrackCommand.isEnabled = true commandCenter.previousTrackCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in self.handlePrevTrack() return .success } commandCenter.previousTrackCommand.isEnabled = true // //MARK:- CommandCenter Scrubbing // commandCenter.changePlaybackPositionCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in // self.handleCurrentTimeSliderChange(self) // // return .success // } // commandCenter.changePlaybackPositionCommand.isEnabled = true // commandCenter.changePlaybackPositionCommand.addTarget(self, action: #selector(handleCurrentTimeSliderChange(_:))) }
ZeroSingularity
4 years ago
Looks like there's an answer for this problem here: https://forums.developer.apple.com/thread/121540 Once I get a solution working I will share here.
Pavlos Nicolaou
4 years ago
Does anyone have the same problems with the Xcode11? The MainTabBar and MiniPlayer are pushed up when I minimized the Player! Thank you :)
ZeroSingularity
4 years ago
The problem arrises when calling layoutIfNeeded() before you call .identity on the tabBar transform. @objc func minimizePlayerDetails() { // the order of these calls prevents layout warnings maximizedTopAnchorConstraint.isActive = false bottomAnchorConstraint.constant = view.frame.height minimizedTopAnchorConstraint.isActive = true UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: { self.tabBar.transform = .identity self.view.layoutIfNeeded() self.playerDetailsView.maximizedStackView.alpha = 0 self.playerDetailsView.miniPlayerView.alpha = 1 }) }
abeldemoz
4 years ago
Hey Brian, could you add an episode showing how to include chapters?
Diego Resnik
3 years ago
I would love to see how to implement this interface with all these constraints to support also landscape. rotating this app a few times, the player jumps in the middle of the screen with these messages: 2020-10-16 16:35:03.725376+0300 PodcastsCourseLBTA[5612:203705] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) ( "<NSAutoresizingMaskLayoutConstraint:0x6000024ffe80 h=-&- v=-&- UILayoutContainerView:0x7ff11f205920.minX == 0 (active, names: '|':UIDropShadowView:0x7ff11f523640 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024ffed0 h=-&- v=-&- H:[UILayoutContainerView:0x7ff11f205920]-(0)-| (active, names: '|':UIDropShadowView:0x7ff11f523640 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024fff20 h=-&- v=-&- UILayoutContainerView:0x7ff11f205920.minY == 0 (active, names: '|':UIDropShadowView:0x7ff11f523640 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024fff70 h=-&- v=-&- V:[UILayoutContainerView:0x7ff11f205920]-(0)-| (active, names: '|':UIDropShadowView:0x7ff11f523640 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f06e0 h=-&- v=-&- UIDropShadowView:0x7ff11f523640.minX == 0 (active, names: '|':UITransitionView:0x7ff11f520320 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f0730 h=-&- v=-&- H:[UIDropShadowView:0x7ff11f523640]-(0)-| (active, names: '|':UITransitionView:0x7ff11f520320 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f0780 h=-&- v=-&- UIDropShadowView:0x7ff11f523640.minY == 0 (active, names: '|':UITransitionView:0x7ff11f520320 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f1bd0 h=-&- v=-&- V:[UIDropShadowView:0x7ff11f523640]-(0)-| (active, names: '|':UITransitionView:0x7ff11f520320 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f1b80 h=-&- v=-&- UITransitionView:0x7ff11f520320.minX == 0 (active, names: '|':UIWindow:0x7ff11f505690 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f1b30 h=-&- v=-&- H:[UITransitionView:0x7ff11f520320]-(0)-| (active, names: '|':UIWindow:0x7ff11f505690 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f1a90 h=-&- v=-&- UITransitionView:0x7ff11f520320.minY == 0 (active, names: '|':UIWindow:0x7ff11f505690 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f1a40 h=-&- v=-&- V:[UITransitionView:0x7ff11f520320]-(0)-| (active, names: '|':UIWindow:0x7ff11f505690 )>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f19f0 h=--- v=--- UIWindow:0x7ff11f505690.width == 896 (active)>", "<NSAutoresizingMaskLayoutConstraint:0x6000024f19a0 h=--- v=--- UIWindow:0x7ff11f505690.height == 414 (active)>", "<NSLayoutConstraint:0x6000024fcf50 UIImageView:0x7ff11cc0efc0.width == UIImageView:0x7ff11cc0efc0.height (active)>", "<NSLayoutConstraint:0x6000024fd9a0 UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide'.bottom == UIStackView:0x7ff11cc041c0.bottom + 24 (active)>", "<NSLayoutConstraint:0x6000024fdb30 UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide'.trailing == UIStackView:0x7ff11cc041c0.trailing + 24 (active)>", "<NSLayoutConstraint:0x6000024fdc70 UIStackView:0x7ff11cc041c0.leading == UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide'.leading + 24 (active)>", "<NSLayoutConstraint:0x6000024fdcc0 UIStackView:0x7ff11cc041c0.top == UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide'.top (active)>", "<NSLayoutConstraint:0x6000024f3d40 V:|-(414)-[PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50] (active, names: '|':UILayoutContainerView:0x7ff11f205920 )>", "<NSLayoutConstraint:0x6000024f3d90 PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50.bottom == UILayoutContainerView:0x7ff11f205920.bottom + 414 (active)>", "<NSLayoutConstraint:0x6000024f3e30 H:|-(0)-[PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50] (active, names: '|':UILayoutContainerView:0x7ff11f205920 )>", "<NSLayoutConstraint:0x6000024f3e80 PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50.trailing == UILayoutContainerView:0x7ff11f205920.trailing (active)>", "<NSLayoutConstraint:0x6000024ff7a0 'UISV-alignment' UIButton:0x7ff11cc082c0'Dismiss'.leading == UIImageView:0x7ff11cc0efc0.leading (active)>", "<NSLayoutConstraint:0x6000024ffb10 'UISV-alignment' UIButton:0x7ff11cc082c0'Dismiss'.trailing == UIImageView:0x7ff11cc0efc0.trailing (active)>", "<NSLayoutConstraint:0x6000024ff430 'UISV-canvas-connection' UIStackView:0x7ff11cc041c0.top == UIButton:0x7ff11cc082c0'Dismiss'.top (active)>", "<NSLayoutConstraint:0x6000024ff5c0 'UISV-canvas-connection' V:[UIStackView:0x7ff11cc146a0]-(0)-| (active, names: '|':UIStackView:0x7ff11cc041c0 )>", "<NSLayoutConstraint:0x6000024ff890 'UISV-canvas-connection' UIStackView:0x7ff11cc041c0.leading == UIButton:0x7ff11cc082c0'Dismiss'.leading (active)>", "<NSLayoutConstraint:0x6000024ff8e0 'UISV-canvas-connection' H:[UIButton:0x7ff11cc082c0'Dismiss']-(0)-| (active, names: '|':UIStackView:0x7ff11cc041c0 )>", "<NSLayoutConstraint:0x6000024ff610 'UISV-spacing' V:[UIButton:0x7ff11cc082c0'Dismiss']-(5)-[UIImageView:0x7ff11cc0efc0] (active)>", "<NSLayoutConstraint:0x6000024ff660 'UISV-spacing' V:[UIImageView:0x7ff11cc0efc0]-(5)-[UISlider:0x7ff11cc0f590] (active)>", "<NSLayoutConstraint:0x6000024ff6b0 'UISV-spacing' V:[UISlider:0x7ff11cc0f590]-(5)-[UIStackView:0x7ff11cc0ff20] (active)>", "<NSLayoutConstraint:0x6000024ff700 'UISV-spacing' V:[UIStackView:0x7ff11cc0ff20]-(5)-[UILabel:0x7ff11cc11190] (active)>", "<NSLayoutConstraint:0x6000024ff750 'UISV-spacing' V:[UILabel:0x7ff11cc11190]-(5)-[UILabel:0x7ff11cc11d50] (active)>", "<NSLayoutConstraint:0x6000024ff7f0 'UISV-spacing' V:[UILabel:0x7ff11cc11d50]-(5)-[UIStackView:0x7ff11cc11fc0] (active)>", "<NSLayoutConstraint:0x6000024ff840 'UISV-spacing' V:[UIStackView:0x7ff11cc11fc0]-(5)-[UIStackView:0x7ff11cc146a0] (active)>", "<NSLayoutConstraint:0x6000024fda90 'UIViewSafeAreaLayoutGuide-bottom' V:[UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide']-(0)-| (active, names: '|':PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50 )>", "<NSLayoutConstraint:0x6000024fda40 'UIViewSafeAreaLayoutGuide-left' H:|-(0)-[UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide'](LTR) (active, names: '|':PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50 )>", "<NSLayoutConstraint:0x6000024fdae0 'UIViewSafeAreaLayoutGuide-right' H:[UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide']-(0)-|(LTR) (active, names: '|':PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50 )>", "<NSLayoutConstraint:0x6000024fd9f0 'UIViewSafeAreaLayoutGuide-top' V:|-(0)-[UILayoutGuide:0x600003e9d420'UIViewSafeAreaLayoutGuide'] (active, names: '|':PodcastsCourseLBTA.PlayerDetailsView:0x7ff11cc04b50 )>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x6000024fcf50 UIImageView:0x7ff11cc0efc0.width == UIImageView:0x7ff11cc0efc0.height (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
HELP & SUPPORT