From 58e18290b721f360887c0db86d9e53e4f06225b9 Mon Sep 17 00:00:00 2001 From: Tiago Martinho Date: Tue, 18 Jun 2019 11:41:28 +0200 Subject: [PATCH] Use Google cast Player --- CastVideos-ios.xcodeproj/project.pbxproj | 4 + .../Classes/GoogleCastPlayer.swift | 104 +++++ .../Classes/MediaTableViewController.swift | 389 +++++++----------- Resources/Main.storyboard | 61 +-- 4 files changed, 275 insertions(+), 283 deletions(-) create mode 100644 CastVideos-swift/Classes/GoogleCastPlayer.swift diff --git a/CastVideos-ios.xcodeproj/project.pbxproj b/CastVideos-ios.xcodeproj/project.pbxproj index 95fd084..5b76e2b 100644 --- a/CastVideos-ios.xcodeproj/project.pbxproj +++ b/CastVideos-ios.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ 10FCA0551E42AF6700BE6122 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F233F5D1CA4776100E484F1 /* Accelerate.framework */; }; 10FCA0561E42AF6C00BE6122 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FAEDDF51CF77F8000E1691F /* libc++.tbd */; }; 2404317022B8E61400071A09 /* GoogleCastButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2404316F22B8E61400071A09 /* GoogleCastButton.swift */; }; + 2404317222B8E68600071A09 /* GoogleCastPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2404317122B8E68600071A09 /* GoogleCastPlayer.swift */; }; 6F1300911CB47B7A0015E8D4 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F1300901CB47B7A0015E8D4 /* Default-568h@2x.png */; }; 6F233EF31CA4610200E484F1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6F233EBA1CA4610200E484F1 /* Images.xcassets */; }; 6F233F261CA4610200E484F1 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 6F233EED1CA4610200E484F1 /* Settings.bundle */; }; @@ -152,6 +153,7 @@ /* Begin PBXFileReference section */ 10FC9FF51E42A16F00BE6122 /* CastVideos-swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CastVideos-swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2404316F22B8E61400071A09 /* GoogleCastButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleCastButton.swift; sourceTree = ""; }; + 2404317122B8E68600071A09 /* GoogleCastPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleCastPlayer.swift; sourceTree = ""; }; 637E433DD77556EEE12772C9 /* Pods_CastVideos_objc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CastVideos_objc.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6F1300901CB47B7A0015E8D4 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "Resources/Default-568h@2x.png"; sourceTree = ""; }; 6F233EBA1CA4610200E484F1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Resources/Images.xcassets; sourceTree = ""; }; @@ -462,6 +464,7 @@ isa = PBXGroup; children = ( 2404316F22B8E61400071A09 /* GoogleCastButton.swift */, + 2404317122B8E68600071A09 /* GoogleCastPlayer.swift */, D5EA0403217544B700013FC8 /* MediaTableViewController.swift */, 2404316E22B8E4D900071A09 /* New Group */, ); @@ -815,6 +818,7 @@ D5EA0410217544B700013FC8 /* MediaItem.swift in Sources */, D5EA0413217544B700013FC8 /* LocalPlayerView.swift in Sources */, D5EA040E217544B700013FC8 /* RootContainerViewController.swift in Sources */, + 2404317222B8E68600071A09 /* GoogleCastPlayer.swift in Sources */, D5EA0411217544B700013FC8 /* ActionSheet.swift in Sources */, D5EA0416217544B800013FC8 /* Toast.swift in Sources */, D5EA0415217544B700013FC8 /* MediaListModel.swift in Sources */, diff --git a/CastVideos-swift/Classes/GoogleCastPlayer.swift b/CastVideos-swift/Classes/GoogleCastPlayer.swift new file mode 100644 index 0000000..fe0a820 --- /dev/null +++ b/CastVideos-swift/Classes/GoogleCastPlayer.swift @@ -0,0 +1,104 @@ +import GoogleCast + +class GoogleCastPlayer: NSObject { + + var hasConnectedSession: Bool { return sessionManager?.hasConnectedSession() ?? false } + + private var sessionManager: GCKSessionManager? { return GCKCastContext.sharedInstance().sessionManager } + private var castSession: GCKCastSession? { return sessionManager?.currentSession as? GCKCastSession } + + var progress: Double { + guard let client = castSession?.remoteMediaClient else { return 0.0 } + let approximateStreamPosition = client.approximateStreamPosition() + let mediaInfo = client.mediaStatus?.currentQueueItem?.mediaInformation + guard let streamDuration = mediaInfo?.streamDuration else { return 0.0 } + if streamDuration <= 0 { return 0.0 } + return approximateStreamPosition / streamDuration + } + + func addListener() { + sessionManager?.add(self) + } + + func removeListener() { + sessionManager?.remove(self) + } + + func play(mediaInfo: GCKMediaInformation) { + let builder = GCKMediaQueueItemBuilder() + builder.mediaInformation = mediaInfo + builder.autoplay = true + builder.preloadTime = TimeInterval(30) + let item = builder.build() + let options = GCKMediaQueueLoadOptions() + options.repeatMode = .off + let request = castSession?.remoteMediaClient?.queueLoad([item], with: options) + request?.delegate = self + castSession?.remoteMediaClient?.add(self) + GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() + } +} + +extension GoogleCastPlayer: GCKSessionManagerListener { + func sessionManager(_: GCKSessionManager, didStart _: GCKSession) { + } + + func sessionManager(_: GCKSessionManager, didResumeSession _: GCKSession) { + } + + func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) { + + } + + func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError _: Error?) { + + } + + func sessionManager(_: GCKSessionManager, + didFailToResumeSession _: GCKSession, withError _: Error?) { + + } +} + +extension GoogleCastPlayer: GCKRemoteMediaClientListener { + func remoteMediaClient(_: GCKRemoteMediaClient, didStartMediaSessionWithID _: Int) { + + } + + func remoteMediaClient(_: GCKRemoteMediaClient, didUpdate mediaStatus: GCKMediaStatus?) { + guard let state = mediaStatus?.playerState else { return } + print("GoogleCastPlayer State: \(state.description)") + } +} + +extension GoogleCastPlayer: GCKRequestDelegate { + func request(_: GCKRequest, didFailWithError _: GCKError) { + } + + func request(_: GCKRequest, didAbortWith _: GCKRequestAbortReason) { + } + + func requestDidComplete(_ request: GCKRequest) { + if request.error != nil { + } + } +} + +extension GCKMediaPlayerState { + var description: String { + switch self { + case .buffering: + return "buffering" + case .playing: + return "playing" + case .paused: + return "paused" + case .idle: + return "idle" + case .loading: + return "loading" + default: + return "unknown" + } + } +} diff --git a/CastVideos-swift/Classes/MediaTableViewController.swift b/CastVideos-swift/Classes/MediaTableViewController.swift index 81f1eb5..db70cf2 100755 --- a/CastVideos-swift/Classes/MediaTableViewController.swift +++ b/CastVideos-swift/Classes/MediaTableViewController.swift @@ -4,277 +4,180 @@ import UIKit let kPrefMediaListURL: String = "media_list_url" @objc(MediaTableViewController) -class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate { - private var sessionManager: GCKSessionManager! - private var castSession: GCKCastSession! - private var rootTitleView: UIImageView! - private var titleView: UIView! - private var mediaListURL: URL! - private var actionSheet: ActionSheet! - private var selectedItem: MediaItem! - - /** The media to be displayed. */ - var mediaList: MediaListModel? - var rootItem: MediaItem? { - didSet { - title = rootItem?.title - tableView.reloadData() +class MediaTableViewController: UITableViewController, MediaListModelDelegate { + private let castPlayer = GoogleCastPlayer() + private var rootTitleView: UIImageView! + private var titleView: UIView! + private var mediaListURL: URL! + private var actionSheet: ActionSheet! + private var selectedItem: MediaItem! + + /** The media to be displayed. */ + var mediaList: MediaListModel? + var rootItem: MediaItem? { + didSet { + title = rootItem?.title + tableView.reloadData() + } } - } - override func viewDidLoad() { - print("MediaTableViewController - viewDidLoad") - super.viewDidLoad() - sessionManager = GCKCastContext.sharedInstance().sessionManager - sessionManager.add(self) - titleView = navigationItem.titleView - rootTitleView = UIImageView(image: UIImage(named: "logo_castvideos.png")) - NotificationCenter.default.addObserver(self, selector: #selector(loadMediaList), - name: UserDefaults.didChangeNotification, object: nil) - if rootItem == nil { - loadMediaList() + override func viewDidLoad() { + print("MediaTableViewController - viewDidLoad") + super.viewDidLoad() + titleView = navigationItem.titleView + rootTitleView = UIImageView(image: UIImage(named: "logo_castvideos.png")) + NotificationCenter.default.addObserver(self, selector: #selector(loadMediaList), + name: UserDefaults.didChangeNotification, object: nil) + if rootItem == nil { + loadMediaList() + } + GoogleCastButton.add(to: navigationItem) + tableView.separatorColor = UIColor.clear + NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), + name: UIDevice.orientationDidChangeNotification, object: nil) + UIDevice.current.beginGeneratingDeviceOrientationNotifications() } - GoogleCastButton.add(to: navigationItem) - tableView.separatorColor = UIColor.clear - NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), - name: UIDevice.orientationDidChangeNotification, object: nil) - UIDevice.current.beginGeneratingDeviceOrientationNotifications() - } - @objc func deviceOrientationDidChange(_: Notification) { - tableView.reloadData() - } + @objc func deviceOrientationDidChange(_: Notification) { + tableView.reloadData() + } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - print("viewWillAppear - Table view") - navigationController?.navigationBar.isTranslucent = false - navigationController?.navigationBar.setBackgroundImage(nil, for: .default) - navigationController?.navigationBar.shadowImage = nil - UIApplication.shared.setStatusBarHidden(false, with: .fade) - navigationController?.interactivePopGestureRecognizer?.isEnabled = true + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + castPlayer.addListener() + customizeNavBar() + appDelegate?.isCastControlBarsEnabled = true + } - if rootItem?.parent == nil { - // If this is the root group, show stylized application title in the title - // view. - navigationItem.titleView = rootTitleView - } else { - // Otherwise show the title of the group in the title view. - navigationItem.titleView = titleView - title = rootItem?.title + private func customizeNavBar() { + navigationController?.navigationBar.isTranslucent = false + navigationController?.navigationBar.setBackgroundImage(nil, for: .default) + navigationController?.navigationBar.shadowImage = nil + UIApplication.shared.setStatusBarHidden(false, with: .fade) + navigationController?.interactivePopGestureRecognizer?.isEnabled = true + if rootItem?.parent == nil { + navigationItem.titleView = rootTitleView + } else { + navigationItem.titleView = titleView + title = rootItem?.title + } } - appDelegate?.isCastControlBarsEnabled = true - } - // MARK: - Table View + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + castPlayer.removeListener() + } - override func numberOfSections(in _: UITableView) -> Int { - return 1 - } + // MARK: - Table View - override func tableView(_: UITableView, - numberOfRowsInSection _: Int) -> Int { - if let rootItem = rootItem { - return rootItem.children.count - } else { - return 0 + override func numberOfSections(in _: UITableView) -> Int { + return 1 } - } - override func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "MediaCell", - for: indexPath) - guard let item = rootItem?.children[indexPath.row] as? MediaItem else { return cell } - var detail: String? - if let mediaInfo = item.mediaInfo { - detail = mediaInfo.metadata?.string(forKey: kGCKMetadataKeyStudio) - if detail == nil { - detail = mediaInfo.metadata?.string(forKey: kGCKMetadataKeyArtist) - } + override func tableView(_: UITableView, + numberOfRowsInSection _: Int) -> Int { + if let rootItem = rootItem { + return rootItem.children.count + } else { + return 0 + } } - if let mediaTitle = (cell.viewWithTag(1) as? UILabel) { - let titleText = item.title - let ownerText = detail - let text = "\(titleText ?? "")\n\(ownerText ?? "")" - let attribs = [NSAttributedString.Key.foregroundColor: mediaTitle.textColor, - NSAttributedString.Key.font: mediaTitle.font] as [NSAttributedString.Key: Any] - let attributedText = NSMutableAttributedString(string: text, attributes: attribs) - let blackColor = UIColor.black - let titleTextRange = NSRange(location: 0, length: (titleText?.count ?? 0)) - attributedText.setAttributes([NSAttributedString.Key.foregroundColor: blackColor], range: titleTextRange) - let lightGrayColor = UIColor.lightGray - let ownerTextRange = NSRange(location: (titleText?.count ?? 0) + 1, - length: (ownerText?.count ?? 0)) - attributedText.setAttributes([NSAttributedString.Key.foregroundColor: lightGrayColor, - NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(12))], range: ownerTextRange) - mediaTitle.attributedText = attributedText + override func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "MediaCell", + for: indexPath) + guard let item = rootItem?.children[indexPath.row] as? MediaItem else { return cell } + var detail: String? + if let mediaInfo = item.mediaInfo { + detail = mediaInfo.metadata?.string(forKey: kGCKMetadataKeyStudio) + if detail == nil { + detail = mediaInfo.metadata?.string(forKey: kGCKMetadataKeyArtist) + } + } + if let mediaTitle = (cell.viewWithTag(1) as? UILabel) { + let titleText = item.title + let ownerText = detail + let text = "\(titleText ?? "")\n\(ownerText ?? "")" + + let attribs = [NSAttributedString.Key.foregroundColor: mediaTitle.textColor, + NSAttributedString.Key.font: mediaTitle.font] as [NSAttributedString.Key: Any] + let attributedText = NSMutableAttributedString(string: text, attributes: attribs) + let blackColor = UIColor.black + let titleTextRange = NSRange(location: 0, length: (titleText?.count ?? 0)) + attributedText.setAttributes([NSAttributedString.Key.foregroundColor: blackColor], range: titleTextRange) + let lightGrayColor = UIColor.lightGray + let ownerTextRange = NSRange(location: (titleText?.count ?? 0) + 1, + length: (ownerText?.count ?? 0)) + attributedText.setAttributes([NSAttributedString.Key.foregroundColor: lightGrayColor, + NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(12))], range: ownerTextRange) + mediaTitle.attributedText = attributedText + } + let mediaOwner = (cell.viewWithTag(2) as? UILabel) + cell.accessoryType = .none + if let imageView = (cell.contentView.viewWithTag(3) as? UIImageView), let imageURL = item.imageURL { + GCKCastContext.sharedInstance().imageCache?.fetchImage(for: imageURL, completion: { (_ image: UIImage?) -> Void in + imageView.image = image + cell.setNeedsLayout() + }) + } + return cell } - let mediaOwner = (cell.viewWithTag(2) as? UILabel) - mediaOwner?.isHidden = true - if item.mediaInfo != nil { - cell.accessoryType = .none - } else { - cell.accessoryType = .disclosureIndicator - } - if let imageView = (cell.contentView.viewWithTag(3) as? UIImageView), let imageURL = item.imageURL { - GCKCastContext.sharedInstance().imageCache?.fetchImage(for: imageURL, completion: { (_ image: UIImage?) -> Void in - imageView.image = image - cell.setNeedsLayout() - }) + override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { + selectedItem = (rootItem?.children[indexPath.row] as? MediaItem) + playSelectedItemRemotely() } - let addButton: UIButton? = (cell.viewWithTag(4) as? UIButton) - let hasConnectedCastSession: Bool = GCKCastContext.sharedInstance().sessionManager.hasConnectedCastSession() - if hasConnectedCastSession { - addButton?.isHidden = false - addButton?.addTarget(self, action: #selector(playButtonClicked), for: .touchDown) - } else { - addButton?.isHidden = true - } - return cell - } - @IBAction func playButtonClicked(_ sender: Any) { - guard let tableViewCell = (sender as AnyObject).superview??.superview as? UITableViewCell else { return } - guard let indexPathForCell = tableView.indexPath(for: tableViewCell) else { return } - selectedItem = (rootItem?.children[indexPathForCell.row] as? MediaItem) - let hasConnectedCastSession: Bool = GCKCastContext.sharedInstance().sessionManager.hasConnectedCastSession() - if selectedItem.mediaInfo != nil, hasConnectedCastSession { - // Display an popover to allow the user to add to queue or play - // immediately. - if actionSheet == nil { - actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel") - actionSheet.addAction(withTitle: "Play Now", target: self, - selector: #selector(playSelectedItemRemotely)) - } - actionSheet.present(in: self, sourceView: tableViewCell) + @objc func playSelectedItemRemotely() { + loadSelectedItem(byAppending: false) +// GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() } - } - - override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { - selectedItem = (rootItem?.children[indexPath.row] as? MediaItem) - playSelectedItemRemotely() - } - @objc func playSelectedItemRemotely() { - loadSelectedItem(byAppending: false) - GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() - } - - func loadSelectedItem(byAppending appending: Bool) { - print("enqueue item \(String(describing: selectedItem.mediaInfo))") - if let remoteMediaClient = GCKCastContext.sharedInstance().sessionManager.currentCastSession?.remoteMediaClient { - let builder = GCKMediaQueueItemBuilder() - builder.mediaInformation = selectedItem.mediaInfo - builder.autoplay = true - builder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime)) - let item = builder.build - if remoteMediaClient.mediaStatus != nil, appending { - let request = remoteMediaClient.queueInsert(item(), beforeItemWithID: kGCKMediaQueueInvalidItemID) - request.delegate = self - } else { - let options = GCKMediaQueueLoadOptions() - options.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off - castSession = sessionManager.currentSession as? GCKCastSession - let request = castSession.remoteMediaClient?.queueLoad([item()], with: options) - request?.delegate = self - } + func loadSelectedItem(byAppending appending: Bool) { + guard let mediaInfo = selectedItem.mediaInfo else { return } + castPlayer.play(mediaInfo: mediaInfo) } - } - - func getSelectedItem() -> MediaItem? { - guard let indexPath = tableView.indexPathForSelectedRow else { return nil } - print("selected row is \(indexPath)") - return (rootItem?.children[(indexPath.row)] as? MediaItem) - } - // MARK: - MediaListModelDelegate - - func mediaListModelDidLoad(_: MediaListModel) { - rootItem = mediaList?.rootItem - title = mediaList?.title - tableView.reloadData() - } - - func mediaListModel(_: MediaListModel, didFailToLoadWithError _: Error?) { - let errorMessage: String = "Unable to load the media list from\n\(mediaListURL.absoluteString)." - let alert = UIAlertView(title: NSLocalizedString("Cast Error", comment: ""), - message: NSLocalizedString(errorMessage, comment: ""), - delegate: nil, cancelButtonTitle: NSLocalizedString("OK", comment: ""), - otherButtonTitles: "") - alert.show() - } - - @objc func loadMediaList() { - // Look up the media list URL. - let userDefaults = UserDefaults.standard - guard let urlKey = userDefaults.string(forKey: kPrefMediaListURL) else { return } - guard let urlText = userDefaults.string(forKey: urlKey) else { return } - let _mediaListURL = URL(string: urlText) - if mediaListURL == _mediaListURL { - // The URL hasn't changed; do nothing. - return + func getSelectedItem() -> MediaItem? { + guard let indexPath = tableView.indexPathForSelectedRow else { return nil } + print("selected row is \(indexPath)") + return (rootItem?.children[(indexPath.row)] as? MediaItem) } - mediaListURL = _mediaListURL - // Asynchronously load the media json. - guard let delegate = (UIApplication.shared.delegate as? AppDelegate) else { return } - delegate.mediaList = MediaListModel() - mediaList = delegate.mediaList - mediaList?.delegate = self - mediaList?.load(from: mediaListURL) - } - - // MARK: - GCKSessionManagerListener - - func sessionManager(_: GCKSessionManager, didStart session: GCKSession) { - print("MediaViewController: sessionManager didStartSession \(session)") - tableView.reloadData() - } - func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) { - print("MediaViewController: sessionManager didResumeSession \(session)") - tableView.reloadData() - } + // MARK: - MediaListModelDelegate - func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) { - print("session ended with error: \(String(describing: error))") - let message = "The Casting session has ended.\n\(String(describing: error))" - if let window = appDelegate?.window { - Toast.displayMessage(message, for: 3, in: window) + func mediaListModelDidLoad(_: MediaListModel) { + rootItem = mediaList?.rootItem + title = mediaList?.title + tableView.reloadData() } - tableView.reloadData() - } - func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) { - if let error = error { - showAlert(withTitle: "Failed to start a session", message: error.localizedDescription) + func mediaListModel(_: MediaListModel, didFailToLoadWithError _: Error?) { + let errorMessage: String = "Unable to load the media list from\n\(mediaListURL.absoluteString)." + let alert = UIAlertView(title: NSLocalizedString("Cast Error", comment: ""), + message: NSLocalizedString(errorMessage, comment: ""), + delegate: nil, cancelButtonTitle: NSLocalizedString("OK", comment: ""), + otherButtonTitles: "") + alert.show() } - tableView.reloadData() - } - func sessionManager(_: GCKSessionManager, - didFailToResumeSession _: GCKSession, withError _: Error?) { - if let window = UIApplication.shared.delegate?.window { - Toast.displayMessage("The Casting session could not be resumed.", for: 3, in: window) + @objc func loadMediaList() { + // Look up the media list URL. + let userDefaults = UserDefaults.standard + guard let urlKey = userDefaults.string(forKey: kPrefMediaListURL) else { return } + guard let urlText = userDefaults.string(forKey: urlKey) else { return } + let _mediaListURL = URL(string: urlText) + if mediaListURL == _mediaListURL { + // The URL hasn't changed; do nothing. + return + } + mediaListURL = _mediaListURL + // Asynchronously load the media json. + guard let delegate = (UIApplication.shared.delegate as? AppDelegate) else { return } + delegate.mediaList = MediaListModel() + mediaList = delegate.mediaList + mediaList?.delegate = self + mediaList?.load(from: mediaListURL) } - tableView.reloadData() - } - - func showAlert(withTitle title: String, message: String) { - let alert = UIAlertView(title: title, message: message, - delegate: nil, cancelButtonTitle: "OK", otherButtonTitles: "") - alert.show() - } - - // MARK: - GCKRequestDelegate - - func requestDidComplete(_ request: GCKRequest) { - print("request \(Int(request.requestID)) completed") - } - - func request(_ request: GCKRequest, didFailWithError error: GCKError) { - print("request \(Int(request.requestID)) failed with error \(error)") - } } diff --git a/Resources/Main.storyboard b/Resources/Main.storyboard index fa93f85..70f1933 100644 --- a/Resources/Main.storyboard +++ b/Resources/Main.storyboard @@ -1,7 +1,11 @@ - + + + + - + + @@ -28,13 +32,13 @@ - - - - - - - - - - - @@ -98,8 +83,8 @@ - - + + @@ -119,7 +104,7 @@ - + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. @@ -212,10 +197,10 @@ - + - + @@ -223,13 +208,13 @@ - + - - - - +