From 5040f771f6370e6a5b194a92a9ae0aec48ea55f7 Mon Sep 17 00:00:00 2001 From: Nicholas Ventimiglia Date: Thu, 19 Sep 2024 12:18:16 -0700 Subject: [PATCH] Fixed Swift APIDemo custom playback controls issue. PiperOrigin-RevId: 676514146 --- .../APIDemo/APIDemo.xcodeproj/project.pbxproj | 4 + ...ManagerCustomVideoControlsController.swift | 35 +++++-- .../APIDemo/Assets.xcassets/Contents.json | 6 +- .../video_mute.imageset/Contents.json | 21 +++++ .../video_mute.imageset/video_mute.png | Bin 0 -> 542 bytes .../video_pause.imageset/Contents.json | 21 +++++ .../video_pause.imageset/video_pause.png | Bin 0 -> 120 bytes .../video_play.imageset/Contents.json | 21 +++++ .../video_play.imageset/video_play.png | Bin 0 -> 294 bytes .../video_unmute.imageset/Contents.json | 21 +++++ .../video_unmute.imageset/video_unmute.png | Bin 0 -> 489 bytes .../APIDemo/Base.lproj/Main.storyboard | 82 ++-------------- .../APIDemo/APIDemo/CustomControls.xib | 63 +++++++++++++ .../APIDemo/APIDemo/CustomControlsView.swift | 88 +++++++----------- .../APIDemo/APIDemo/SimpleNativeAdView.swift | 33 +++++-- .../APIDemo/APIDemo/SimpleNativeAdView.xib | 26 +++--- 16 files changed, 259 insertions(+), 162 deletions(-) create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/Contents.json create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/video_mute.png create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_pause.imageset/Contents.json create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_pause.imageset/video_pause.png create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_play.imageset/Contents.json create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_play.imageset/video_play.png create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_unmute.imageset/Contents.json create mode 100644 Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_unmute.imageset/video_unmute.png create mode 100644 Swift/advanced/APIDemo/APIDemo/CustomControls.xib diff --git a/Swift/advanced/APIDemo/APIDemo.xcodeproj/project.pbxproj b/Swift/advanced/APIDemo/APIDemo.xcodeproj/project.pbxproj index c63a3f9e..7d2749dc 100644 --- a/Swift/advanced/APIDemo/APIDemo.xcodeproj/project.pbxproj +++ b/Swift/advanced/APIDemo/APIDemo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 1CD352682C9CC97200534FCC /* CustomControls.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1CD352672C9CC97200534FCC /* CustomControls.xib */; }; 4A7A6CD71C76237500FB1A32 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7A6CD61C76237500FB1A32 /* Constants.swift */; }; 4AA7D6911C625A1200DFD2EB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA7D6901C625A1200DFD2EB /* AppDelegate.swift */; }; 4AA7D6961C625A1200DFD2EB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA7D6941C625A1200DFD2EB /* Main.storyboard */; }; @@ -33,6 +34,7 @@ /* Begin PBXFileReference section */ 15CB61211C7D0256000212DE /* APIDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "APIDemo-Bridging-Header.h"; sourceTree = ""; }; + 1CD352672C9CC97200534FCC /* CustomControls.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomControls.xib; sourceTree = ""; }; 4A7A6CD61C76237500FB1A32 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 4AA7D68D1C625A1200DFD2EB /* APIDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = APIDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4AA7D6901C625A1200DFD2EB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -125,6 +127,7 @@ AED11115203213DF00EA4BEE /* AdManagerCustomVideoControls */ = { isa = PBXGroup; children = ( + 1CD352672C9CC97200534FCC /* CustomControls.xib */, 507818F1219A417F00E5A44A /* AdManagerCustomVideoControlsController.swift */, AED11117203213F300EA4BEE /* SimpleNativeAdView.xib */, AED11118203213F500EA4BEE /* UnifiedNativeAdView.xib */, @@ -205,6 +208,7 @@ 4AA7D69B1C625A1200DFD2EB /* LaunchScreen.storyboard in Resources */, 4AA7D6981C625A1200DFD2EB /* Assets.xcassets in Resources */, 4AA7D6961C625A1200DFD2EB /* Main.storyboard in Resources */, + 1CD352682C9CC97200534FCC /* CustomControls.xib in Resources */, AED11120203213FC00EA4BEE /* UnifiedNativeAdView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Swift/advanced/APIDemo/APIDemo/AdManagerCustomVideoControlsController.swift b/Swift/advanced/APIDemo/APIDemo/AdManagerCustomVideoControlsController.swift index e777ade9..f3c88a7b 100644 --- a/Swift/advanced/APIDemo/APIDemo/AdManagerCustomVideoControlsController.swift +++ b/Swift/advanced/APIDemo/APIDemo/AdManagerCustomVideoControlsController.swift @@ -19,8 +19,8 @@ import GoogleMobileAds import UIKit -private let testAdUnit = "/21775744923/example/native-video" -private let testNativeCustomFormatID = "12406343" +private let testAdUnit = "/6499/example/native-video" +private let testNativeCustomFormatID = "10104090" class AdManagerCustomVideoControlsController: UIViewController { /// Switch to indicate if video ads should start muted. @@ -33,8 +33,6 @@ class AdManagerCustomVideoControlsController: UIViewController { @IBOutlet weak var unifiedNativeAdSwitch: UISwitch! /// Switch to custom native ads. @IBOutlet weak var customNativeAdSwitch: UISwitch! - /// View containing information about video and custom controls. - @IBOutlet weak var customControlsView: CustomControlsView! /// Refresh the native ad. @IBOutlet weak var refreshButton: UIButton! /// The Google Mobile Ads SDK version number label. @@ -47,7 +45,7 @@ class AdManagerCustomVideoControlsController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - versionLabel.text = "\(GADMobileAds.sharedInstance().versionNumber)" + versionLabel.text = GADGetStringFromVersionNumber(GADMobileAds.sharedInstance().versionNumber) refreshAd(nil) } @IBAction func refreshAd(_ sender: Any?) { @@ -69,7 +67,6 @@ class AdManagerCustomVideoControlsController: UIViewController { refreshButton.isEnabled = false adLoader = GADAdLoader( adUnitID: testAdUnit, rootViewController: self, adTypes: adTypes, options: [videoOptions]) - customControlsView.reset(withStartMuted: videoOptions.startMuted) adLoader?.delegate = self adLoader?.load(GAMRequest()) } @@ -147,8 +144,6 @@ extension AdManagerCustomVideoControlsController: GADNativeAdLoaderDelegate { heightConstraint.isActive = true } - customControlsView.mediaContent = nativeAd.mediaContent - // These assets are not guaranteed to be present. Check that they are before // showing or hiding them. (nativeAdView.bodyView as? UILabel)?.text = nativeAd.body @@ -180,6 +175,26 @@ extension AdManagerCustomVideoControlsController: GADNativeAdLoaderDelegate { // required to make the ad clickable. // Note: this should always be done after populating the ad views. nativeAdView.nativeAd = nativeAd + + // [START set_custom_video_controls] + // Add custom video controls to replace default video controls. + if nativeAd.mediaContent.hasVideoContent { + if let mediaView = nativeAdView.mediaView { + if let customControlsView = Bundle.main.loadNibNamed( + "CustomControls", owner: nil, options: nil)?.first as? CustomControlsView + { + customControlsView.mediaContent = mediaView.mediaContent + customControlsView.isMuted = startMutedSwitch.isOn + mediaView.addSubview(customControlsView) + NSLayoutConstraint.activate([ + customControlsView.leadingAnchor.constraint(equalTo: mediaView.leadingAnchor), + customControlsView.bottomAnchor.constraint(equalTo: mediaView.bottomAnchor), + ]) + mediaView.bringSubviewToFront(customControlsView) + } + } + } + // [END set_custom_video_controls] } } @@ -196,8 +211,8 @@ extension AdManagerCustomVideoControlsController: GADCustomNativeAdLoaderDelegat as! SimpleNativeAdView setAdView(simpleNativeAdView) // Populate the custom native ad view with its assets. - simpleNativeAdView.populate(withCustomNativeAd: customNativeAd) - customControlsView.mediaContent = customNativeAd.mediaContent + simpleNativeAdView.populate( + withCustomNativeAd: customNativeAd, startMuted: self.startMutedSwitch.isOn) } func customNativeAdFormatIDs(for adLoader: GADAdLoader) -> [String] { diff --git a/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/Contents.json b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/Contents.json index da4a164c..73c00596 100644 --- a/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/Contents.json +++ b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/Contents.json b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/Contents.json new file mode 100644 index 00000000..4403d6ee --- /dev/null +++ b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "video_mute.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/video_mute.png b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/video_mute.png new file mode 100644 index 0000000000000000000000000000000000000000..53726693f1bba7ec9131ae5ddcf008540cc8cc0b GIT binary patch literal 542 zcmV+(0^$9MP)+9#~?da<5=<4p^9zaC^000GaQchCocbaGY%;n$YLkpeFR0)}aY~yx#** zJ{NjQZz;T6Z7lTB+m?_70OxCPU9elBOcBn2qd^vo2&M@LsJPyw&;$TKqi_kL@E+(_ zo8+SK4piGD@(qZ~aExKN~@0bCFOCTPI?jPo(T1^q?F+(`)h!GT_dxr3kq zSbaW$q+11qJP8^AA-OO)54w<4hzZbzqyi=e0EA>hC_onezk>k4c%cWvcl$}e;7Dji zpR&R8m z(BcJw{j9kn09XGAr*|t42^*Y{{R2aF0jnb11!eG8XSNWn^mCf3ST3eHRdOglsv7B=w6Ibh0+2?&s_4=jrWk;UnPy000GaQchC<|Gz+h&#iSx0004mNklI*o1)%5SqQ;lt6{P_+J_H24dJE!%i2!(d3%Cp|6)?LR|3E;?1&HL= z@mxS#1uHE+7SOH*Naoe20+h>&X_X0dALdw4r#0LUfb1zVp`DFsG zV|oSndI864@X!CE`itf@0eCAY&PCu}KwAXfI$evVL!BQS+FF9vPo}R=h(jNJhn#K8 zmPnT^eG4yUSI54kW28fUWbvyCuxt4q7keC^-hj+(p&Xi9tA;(F)7x0LvY~OCoEvuM zIMECVDBj=B@h-j$xmNtLA-#I>>xQd0JciqvKHHd$>DKaWb3A;sy&qvS5q?gWc}sKAj{B00000NkvXXu0mjfQHj$| literal 0 HcmV?d00001 diff --git a/Swift/advanced/APIDemo/APIDemo/Base.lproj/Main.storyboard b/Swift/advanced/APIDemo/APIDemo/Base.lproj/Main.storyboard index ee2895c5..6c42dd76 100644 --- a/Swift/advanced/APIDemo/APIDemo/Base.lproj/Main.storyboard +++ b/Swift/advanced/APIDemo/APIDemo/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -791,6 +791,7 @@ + @@ -804,76 +805,18 @@ - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -954,7 +897,7 @@ @@ -1010,7 +950,6 @@ - @@ -1020,7 +959,6 @@ - diff --git a/Swift/advanced/APIDemo/APIDemo/CustomControls.xib b/Swift/advanced/APIDemo/APIDemo/CustomControls.xib new file mode 100644 index 00000000..6dc568b3 --- /dev/null +++ b/Swift/advanced/APIDemo/APIDemo/CustomControls.xib @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swift/advanced/APIDemo/APIDemo/CustomControlsView.swift b/Swift/advanced/APIDemo/APIDemo/CustomControlsView.swift index 9be7e31f..30fd7411 100644 --- a/Swift/advanced/APIDemo/APIDemo/CustomControlsView.swift +++ b/Swift/advanced/APIDemo/APIDemo/CustomControlsView.swift @@ -1,20 +1,17 @@ +// Copyright 2024 Google LLC // -// Copyright (C) 2018 Google, Inc. -// -// CustomControlsView.h -// APIDemo -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import Foundation import GoogleMobileAds import UIKit @@ -23,58 +20,43 @@ import UIKit /// custom video controls. Set the video options when requesting an ad, then set the video /// controller when the ad is received. class CustomControlsView: UIView { + + /// The container view for the actual video controls + @IBOutlet weak var controlsView: UIView! + /// The play button. + @IBOutlet weak var playButton: UIButton! + /// The mute button. + @IBOutlet weak var muteButton: UIButton! + /// Resets the controls status, and lets the controls view know the initial mute state. - /// The controller for the ad currently being displayed. Setting this sets up the view according to - /// the video controller state. + /// The controller for the ad currently being displayed. Setting this sets up the view according + /// to the video controller state. weak var mediaContent: GADMediaContent? { didSet { if let mediaContent = mediaContent { controlsView.isHidden = !mediaContent.videoController.customControlsEnabled() mediaContent.videoController.delegate = self - videoStatusLabel.text = - mediaContent.hasVideoContent - ? "Ad contains video content." : "Ad does not contain video content." } else { controlsView.isHidden = true - videoStatusLabel.text = "" } } } - /// The container view for the actual video controls - @IBOutlet weak var controlsView: UIView! - /// The play button. - @IBOutlet weak var playButton: UIButton! - /// The mute button. - @IBOutlet weak var muteButton: UIButton! - /// The label showing the video status for the current video controller. - @IBOutlet weak var videoStatusLabel: UILabel! - /// Boolean reflecting current playback state for UI. - private var _isPlaying = false - var isPlaying: Bool { - get { - return _isPlaying - } - set(playing) { - _isPlaying = playing - let title: String = _isPlaying ? "Pause" : "Play" - playButton.setTitle(title, for: .normal) + var isPlaying = false { + didSet { + let image: UIImage? = + isPlaying ? UIImage(named: "video_pause") : UIImage(named: "video_play") + playButton.setImage(image, for: .normal) } } /// Boolean reflecting current mute state for UI. - private var _isMuted = false - var isMuted: Bool { - get { - return _isMuted - } - set(muted) { - if muted != isMuted { - let title: String = muted ? "Unmute" : "Mute" - muteButton.setTitle(title, for: .normal) - } - _isMuted = muted + var isMuted: Bool? = false { + didSet { + let image: UIImage? = + isMuted == true ? UIImage(named: "video_mute") : UIImage(named: "video_unmute") + muteButton.setImage(image, for: .normal) } } @@ -82,7 +64,6 @@ class CustomControlsView: UIView { isMuted = startMuted isPlaying = false controlsView.isHidden = true - videoStatusLabel.text = "" } @IBAction func playPause(_ sender: Any) { @@ -95,37 +76,32 @@ class CustomControlsView: UIView { mediaContent.videoController.play() } } + @IBAction func muteUnmute(_ sender: Any) { - isMuted = !isMuted if let mediaContent = mediaContent { - mediaContent.videoController.setMute(isMuted) + mediaContent.videoController.setMute(!mediaContent.videoController.isMuted) } } } extension CustomControlsView: GADVideoControllerDelegate { func videoControllerDidPlayVideo(_ videoController: GADVideoController) { - videoStatusLabel.text = "Video did play." print("\(#function)") isPlaying = true } func videoControllerDidPauseVideo(_ videoController: GADVideoController) { - videoStatusLabel.text = "Video did pause." print("\(#function)") isPlaying = false } func videoControllerDidMuteVideo(_ videoController: GADVideoController) { - videoStatusLabel.text = "Video has muted." print("\(#function)") isMuted = true } func videoControllerDidUnmuteVideo(_ videoController: GADVideoController) { - videoStatusLabel.text = "Video has unmuted." print("\(#function)") isMuted = false } func videoControllerDidEndVideoPlayback(_ videoController: GADVideoController) { - videoStatusLabel.text = "Video playback has ended." print("\(#function)") isPlaying = false } diff --git a/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.swift b/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.swift index 787b4e0c..70460437 100644 --- a/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.swift +++ b/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.swift @@ -19,17 +19,16 @@ import GoogleMobileAds import UIKit /// Headline asset key. -private let SimpleNativeAdViewHeadlineKey = "Headline" +private let simpleNativeAdViewHeadlineKey = "Headline" /// Main image asset key. -private let SimpleNativeAdViewMainImageKey = "MainImage" +private let simpleNativeAdViewMainImageKey = "MainImage" /// Caption asset key. -private let SimpleNativeAdViewCaptionKey = "Caption" +private let simpleNativeAdViewCaptionKey = "Caption" /// View representing a custom native ad format with template ID 10063170. class SimpleNativeAdView: UIView { // Weak references to this ad's asset views. @IBOutlet weak var headlineView: UILabel! - @IBOutlet weak var mainPlaceholder: UIView! @IBOutlet weak var captionView: UILabel! @@ -45,11 +44,11 @@ class SimpleNativeAdView: UIView { } @objc func performClickOnHeadline() { - customNativeAd?.performClickOnAsset(withKey: SimpleNativeAdViewHeadlineKey) + customNativeAd?.performClickOnAsset(withKey: simpleNativeAdViewHeadlineKey) } /// Populates the ad view with the custom native ad object. - func populate(withCustomNativeAd customNativeAd: GADCustomNativeAd) { + func populate(withCustomNativeAd customNativeAd: GADCustomNativeAd, startMuted: Bool) { self.customNativeAd = customNativeAd // The custom click handler is an optional block which will override the normal click action // defined by the ad. Pass nil for the click handler to let the SDK process the default click @@ -68,8 +67,9 @@ class SimpleNativeAdView: UIView { alert, animated: true, completion: nil) } // Populate the custom native ad assets. - headlineView.text = customNativeAd.string(forKey: SimpleNativeAdViewHeadlineKey) - captionView.text = customNativeAd.string(forKey: SimpleNativeAdViewCaptionKey) + headlineView.text = customNativeAd.string(forKey: simpleNativeAdViewHeadlineKey) + captionView.text = customNativeAd.string(forKey: simpleNativeAdViewCaptionKey) + // Remove all the media placeholder's subviews. for subview: UIView in mainPlaceholder.subviews { subview.removeFromSuperview() @@ -81,8 +81,23 @@ class SimpleNativeAdView: UIView { let mediaView = GADMediaView() mediaView.mediaContent = customNativeAd.mediaContent mainView = mediaView + // [START set_custom_video_controls] + // Add custom video controls to replace default video controls. + if let customControlsView = Bundle.main.loadNibNamed( + "CustomControls", owner: nil, options: nil)?.first as? CustomControlsView + { + customControlsView.mediaContent = customNativeAd.mediaContent + customControlsView.isMuted = startMuted + mediaView.addSubview(customControlsView) + NSLayoutConstraint.activate([ + customControlsView.leadingAnchor.constraint(equalTo: mediaView.leadingAnchor), + customControlsView.bottomAnchor.constraint(equalTo: mediaView.bottomAnchor), + ]) + mediaView.bringSubviewToFront(customControlsView) + } + // [END set_custom_video_controls] } else { - let image: UIImage? = customNativeAd.image(forKey: SimpleNativeAdViewMainImageKey)?.image + let image: UIImage? = customNativeAd.image(forKey: simpleNativeAdViewMainImageKey)?.image mainView = UIImageView(image: image) } mainPlaceholder.addSubview(mainView) diff --git a/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.xib b/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.xib index 3f7f52b4..fba71ff2 100644 --- a/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.xib +++ b/Swift/advanced/APIDemo/APIDemo/SimpleNativeAdView.xib @@ -1,29 +1,26 @@ - - - - + + - - + - + - - + + @@ -33,7 +30,7 @@ @@ -53,7 +50,12 @@ - + + + + + +