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 00000000..53726693 Binary files /dev/null and b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_mute.imageset/video_mute.png differ diff --git a/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_pause.imageset/Contents.json b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_pause.imageset/Contents.json new file mode 100644 index 00000000..d429a1bc --- /dev/null +++ b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_pause.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "video_pause.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_pause.imageset/video_pause.png b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_pause.imageset/video_pause.png new file mode 100644 index 00000000..c19e51ab Binary files /dev/null and b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_pause.imageset/video_pause.png differ diff --git a/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_play.imageset/Contents.json b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_play.imageset/Contents.json new file mode 100644 index 00000000..cde409e1 --- /dev/null +++ b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_play.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "video_play.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_play.imageset/video_play.png b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_play.imageset/video_play.png new file mode 100644 index 00000000..51ef616d Binary files /dev/null and b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_play.imageset/video_play.png differ diff --git a/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_unmute.imageset/Contents.json b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_unmute.imageset/Contents.json new file mode 100644 index 00000000..0f3697b5 --- /dev/null +++ b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_unmute.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "video_unmute.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_unmute.imageset/video_unmute.png b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_unmute.imageset/video_unmute.png new file mode 100644 index 00000000..e4fc961b Binary files /dev/null and b/Swift/advanced/APIDemo/APIDemo/Assets.xcassets/video_unmute.imageset/video_unmute.png differ 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 @@ - + + + + + +