美文网首页
(Swift) 开屏广告(image、gif、video)

(Swift) 开屏广告(image、gif、video)

作者: 布呐呐u | 来源:发表于2021-09-08 15:11 被阅读0次

首页展示开屏广告

  • 支持本地资源与网络资源
  • 支持网络资源缓存策略
  • 本地image、本地gif、本地video
  • 网络image、网络gif、网络video


    Example

使用方法

  • 3行代码,即可初始化使用
 let adConfig = CCAdConfig(type: .adImage, name: CCAppURL.adImageUrl, url: CCAppURL.adLinkUrl)
 let adViewController = CCAdViewController(config: adConfig)
 adViewController.dismissBlock = { self.window?.rootViewController = CCTabBarController() }

源码分享

//
//  CCAdViewController.swift
//  HelloSwift
//
//  Created by well on 2021/9/6.
//

import UIKit

public enum CCAdType {
case adImage, adGif, adVideo
}

struct CCAdConfig {
    /// 广告类型
    fileprivate var adType: CCAdType
    /// 跳转链接
    fileprivate var linkUrl: String
    /// 资源名称
    fileprivate var resourceName: String
    /// 提示文字(默认'跳过')
    fileprivate var tipsText: String
    /// 是否展示跳过按钮(默认展示)
    fileprivate var isSkip: Bool
    /// 是否展示静音按钮(默认展示,仅video类型)
    fileprivate var isMute: Bool
    /// 广告动画时长(默认10秒)
    fileprivate var adDuration: TimeInterval
    
    init(type: CCAdType, name: String, url: String, text: String = "跳过", skip: Bool = true, mute: Bool = true, duration: TimeInterval = 10) {
        isMute = mute
        isSkip = skip
        adType = type
        linkUrl = url
        tipsText = text
        resourceName = name
        adDuration = duration
    }
}

class CCAdViewController: CCViewController, CCCountDownManageProtocol {
    
    /// 广告视图配置参数
    private var adConfig: CCAdConfig
    /// 广告视图播放数据源
    private var adAVPlayerItem: AVPlayerItem?
    /// 广告视图消失时回调处理
    public var dismissBlock: os_block_t?
    
    /// 懒加载muteButton
    private lazy var muteButton: UIButton = {
        let button = UIButton()
        button.setImage(R.image.ad_mute(), for: .selected)
        button.setImage(R.image.ad_restore(), for: .normal)
        button.addTarget(self, action: #selector(muteButtonDidSeleted), for: .touchUpInside)
        return button
    }()
    
    /// 懒加载skipButton
    private lazy var skipButton: UIButton = {
        let button = UIButton()
        button.layer.cornerRadius = 4
        button.titleLabel?.font = RegularFont(14)
        button.backgroundColor = .hexColor("#000000", 0.5)
        button.addTarget(self, action: #selector(skipButtonDidSeleted), for: .touchUpInside)
        return button
    }()
    
    /// 懒加载adImageView
    private lazy var adImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.layer.masksToBounds = true
        imageView.contentMode = .scaleAspectFill
        return imageView
    }()
    
    /// 懒加载adPlayerController
    private lazy var adPlayerController: AVPlayerViewController = {
        let player = AVPlayerViewController()
        player.showsPlaybackControls = false
        player.videoGravity = .resizeAspectFill
        return player
    }()
    
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    // MARK: - 反初始化器
    deinit { removeAdAVPlayerItemObserver(); print("CCAdViewController deinit~") }
    
    // MARK: - 初始化器
    init(config: CCAdConfig) {
        adConfig = config
        super.init(nibName: nil, bundle: nil)
    }
    
    /// 添加adAVPlayerItem观察者
    private func addAdAVPlayerItemObserver() {
        adAVPlayerItem?.addObserver(self,forKeyPath: "status", options: .new, context: nil)
    }
    
    /// 移除adAVPlayerItem观察者
    private func removeAdAVPlayerItemObserver() {
        adAVPlayerItem?.removeObserver(self, forKeyPath: "status", context: nil)
    }
    
    /// 处理adAVPlayerItem观察者
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard let object = object as? AVPlayerItem  else { return }
        guard let keyPath = keyPath else { return }
        
        if keyPath == "status" {
            if object.status == .readyToPlay {
                // 待视频准备就绪后,添加静音按钮
                if adConfig.isMute { addMuteButton() }
                // 待视频准备就绪后,添加跳过按钮,且开始倒计时
                if adConfig.isSkip { addSkipButton() }
                // 资源准备就绪后,开始播放视频
                adPlayerController.player?.play()
            } else {
                // 容错处理,资源准备失败,则直接移除广告视图,执行后续流程
                dismiss()
            }
                }
    }
    
    override func setUI() {
        // 添加手势
        let tap = UITapGestureRecognizer(target: self, action: #selector(adViewDidSeleted))
        view.addGestureRecognizer(tap)
        
        switch adConfig.adType {
        case .adImage, .adGif: addAdImageView(); break
        case .adVideo: addAdPlayerController(); break
        }
        
        if adConfig.isSkip && adConfig.adType != .adVideo { addSkipButton() }
        
        // 配置显示参数
        configDisPlayParameter()
    }
    
    /// 添加广告muteButton
    func addMuteButton() {
        if adConfig.isMute {
            view.addSubview(muteButton)
            muteButton.snp.makeConstraints { make in
                make.left.equalTo(kAdaptedWidth(36))
                make.top.equalTo(kSafeMarginTop(0))
            }
        }
    }
    
    /// 添加广告skipButton
    func addSkipButton() {
        view.addSubview(skipButton)
        skipButton.snp.makeConstraints { make in
            make.right.equalTo(kAdaptedWidth(-36))
            make.top.equalTo(kSafeMarginTop(0))
            make.size.equalTo(CGSize(width: 70, height: 30))
        }
        
        let startTime = Int(CACurrentMediaTime())
        CCCountDownManage.shared.deletage = self
        CCCountDownManage.shared.run(start: startTime, end: startTime + adConfig.adDuration.i)
    }
    
    /// 添加广告adImageView
    func addAdImageView() {
        view.addSubview(adImageView)
        adImageView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
    
    /// 添加广告adPlayerController
    func addAdPlayerController() {
        view.addSubview(adPlayerController.view)
        adPlayerController.view.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
    
    /// 配置广告相关参数
    func configDisPlayParameter() {
        switch adConfig.adType {
        case .adImage: loadImage(); break
        case .adGif: loadGif(); break
        case .adVideo: loadVideo(); break
        }
    }
    
    /// 加载广告(image)
    private func loadImage() {
        // 非空校验
        guard !adConfig.resourceName.isEmpty else { return }
        
        // 网络资源(image)
        if adConfig.resourceName.hasPrefix("http") {
            // 移除后缀名
            let objString = NSString(string: adConfig.resourceName).deletingPathExtension
            // 不带后缀名的最后一项
            let componentString = NSString(string: objString).lastPathComponent
            // 格式化储存路径
            let adImageTemp = kAppCachesPath + "/" + componentString + ".jpg"
            
            // 无缓存,则获取网络数据后展示
            if !adImageTemp.fileExist() {
                let imgUrl = URL(string: adConfig.resourceName)
                let data = try? Data(contentsOf: imgUrl!)
                
                if let imgData = data {
                    adImageView.image = UIImage(data: imgData)
                    updateAdImage(imgData, adImageTemp)
                }else {
                    // 容错处理,没值则直接移除广告视图,执行后续流程
                    dismiss()
                }
            }else {
                // 有缓存,则直接从缓存读取展示
                let imgUrl = URL(fileURLWithPath: adImageTemp)
                let imgData = try? Data(contentsOf: imgUrl)
                adImageView.image = UIImage(data: imgData!)
            }
        }else {
            // 本地资源(image)
            adImageView.image = UIImage(named: adConfig.resourceName)
        }
    }
    
    /// 加载广告(gif)
    private func loadGif() {
        // 非空校验
        guard !adConfig.resourceName.isEmpty else { return }
        
        // 网络资源(gif)
        if adConfig.resourceName.hasPrefix("http") {
            // 移除后缀名
            let objString = NSString(string: adConfig.resourceName).deletingPathExtension
            // 不带后缀名的最后一项
            let componentString = NSString(string: objString).lastPathComponent
            // 格式化储存路径
            let adGifTemp = kAppCachesPath + "/" + componentString + ".gif"
            
            // 无缓存,则获取网络数据后展示
            if !adGifTemp.fileExist() {
                let imgUrl = URL(string: adConfig.resourceName)
                let data = try? Data(contentsOf: imgUrl!)
                
                if let gifData = data {
                    let gifArray = UIImage.gif(data: gifData)
                    adImageView.animationImages = gifArray.0
                    adImageView.animationDuration = gifArray.1
                    adImageView.startAnimating()
                    updateAdGif(gifData, adGifTemp)
                }else {
                    // 容错处理,没值则直接移除广告视图,执行后续流程
                    dismiss()
                }
            }else {
                // 有缓存,则直接从缓存读取展示
                let gifUrl = URL(fileURLWithPath: adGifTemp)
                let gifData = try? Data(contentsOf: gifUrl)
                let gifArray = UIImage.gif(data: gifData!)
                adImageView.animationImages = gifArray.0
                adImageView.animationDuration = gifArray.1
                adImageView.startAnimating()
            }
        }else {
            // 本地资源(gif)
            let filePath = Bundle.main.path(forResource: adConfig.resourceName, ofType: "gif")!
            let gifUrl = URL(fileURLWithPath: filePath)
            let gifData = try? Data(contentsOf: gifUrl)
            let gifArray = UIImage.gif(data: gifData!)
            adImageView.animationImages = gifArray.0
            adImageView.animationDuration = gifArray.1
            adImageView.startAnimating()
        }
    }
    
    /// 加载广告(video)
    private func loadVideo() {
        // 非空校验
        guard !adConfig.resourceName.isEmpty else { return }
        
        // 网络资源(video)
        if adConfig.resourceName.hasPrefix("http") {
            // 移除后缀名
            let objString = NSString(string: adConfig.resourceName).deletingPathExtension
            // 不带后缀名的最后一项
            let componentString = NSString(string: objString).lastPathComponent
            // 格式化储存路径
            let adVideoTemp = kAppCachesPath + "/" + componentString + ".mp4"
            
            // 无缓存,则获取网络数据后展示
            if !adVideoTemp.fileExist() {
                let videoUrl = URL(string: adConfig.resourceName)!
                let data = try? Data(contentsOf: videoUrl)
                if let videoData = data {
                    adAVPlayerItem = AVPlayerItem(url: videoUrl)
                    let adAVPlayer = AVPlayer(playerItem: adAVPlayerItem)
                    adPlayerController.player = adAVPlayer
                    updateAdVideo(videoData, adVideoTemp)
                }else {
                    // 容错处理,没值则直接移除广告视图,执行后续流程
                    dismiss()
                }
            }else {
                // 有缓存,则直接从缓存读取展示
                let videoUrl = URL(fileURLWithPath: adVideoTemp)
                adAVPlayerItem = AVPlayerItem(url: videoUrl)
                let adAVPlayer = AVPlayer(playerItem: adAVPlayerItem)
                adPlayerController.player = adAVPlayer
            }
        }else {
            // 本地资源(video)
            let filePath = Bundle.main.path(forResource: adConfig.resourceName, ofType: "mp4")
            let videoUrl = URL(fileURLWithPath: filePath!)
            adAVPlayerItem = AVPlayerItem(url: videoUrl)
            let adAVPlayer = AVPlayer(playerItem: adAVPlayerItem)
            adPlayerController.player = adAVPlayer
        }
        addAdAVPlayerItemObserver()
    }
    
    /// 更新广告资源(image)
    public func updateAdImage(_ imageData: Data, _ imageTempPath: String)  {
        DispatchQueue.global(qos: .userInteractive).async {
            let objData = NSData(data: imageData)
            objData.write(toFile: imageTempPath, atomically: true)
        }
    }
    
    /// 更新广告资源(gif)
    public func updateAdGif(_ gifData: Data, _ gifTempPath: String)  {
        DispatchQueue.global(qos: .userInteractive).async {
            let objData = NSData(data: gifData)
            objData.write(toFile: gifTempPath, atomically: true)
        }
    }
    
    /// 更新广告资源(video)
    public func updateAdVideo(_ videoData: Data, _ videoTempPath: String)  {
        DispatchQueue.global(qos: .userInteractive).async {
            let objData = NSData(data: videoData)
            objData.write(toFile: videoTempPath, atomically: true)
        }
    }
    
    /// 移除广告视图
    private func dismiss() {
        UIView.animate(withDuration: 0.25) {
            if self.adConfig.isSkip {
                CCCountDownManage.shared.cannel()
                self.skipButton.removeFromSuperview()
            }
            
            if self.adConfig.adType == .adVideo {
                self.muteButton.removeFromSuperview()
                self.adPlayerController.player?.isMuted = true
                self.adPlayerController.player?.pause()
                self.adPlayerController.view.alpha = 0
            } else {
                self.adImageView.stopAnimating()
                self.adImageView.alpha = 0
            }
                
                } completion: { _ in
                    self.dismissBlock?()
                }
    }
    
    /// 跳过事件
    @objc private func skipButtonDidSeleted() { dismiss() }
    
    /// 静音事件
    @objc private func muteButtonDidSeleted() {
        muteButton.isSelected = !muteButton.isSelected
        adPlayerController.player?.isMuted = muteButton.isSelected
    }
    
    /// 广告连接
    @objc private func adViewDidSeleted() {
        dismiss()
        let url = URL(string: adConfig.linkUrl)
        guard (url != nil) else { return }
        UIApplication.shared.open(url!, options: [:], completionHandler: nil)
    }
    
    /// CCCountDownManageProtocol代理方法
    func refreshTime(result: [String]) {
        let timeStr = result.last!
        if timeStr == "00" { dismiss(); return }
        
        self.skipButton.setTitle("\(adConfig.tipsText)(\(timeStr)"+"s)", for: .normal)
    }
}

更多组件 GitHub

相关文章

网友评论

      本文标题:(Swift) 开屏广告(image、gif、video)

      本文链接:https://www.haomeiwen.com/subject/zxuvwltx.html