Search code examples
swiftstructdrysubclassing

I keep using this same chunk of code over and over. How do I consolidate it?


Being the Swift noob that I am, I find myself copying and pasting code. I know I'm supposed to use the DRY method and not do this, but this particular bit of code has me stumped. I tried creating a struct to hold it, but the struct threw all sorts of errors. I don't quite understand classes and how I would subclass it, and so maybe that's the solution. I just don't know how to do it? Or maybe an extension?

Anyway, here is the code I keep copying and pasting in each new view controller:

import UIKit
import AVKit

class Step3JobSummaryVC: UIViewController, UITableViewDataSource, UITableViewDelegate {

...

var sourceVCIdentity = "setup"

var initialLaunch = true
let playerVC = AVPlayerViewController()
let video = Video.step3JobsSummary

...


// ------------------------
// Autoplay Video Functions
// ------------------------

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if initialLaunch == true {
        showUnplayedVideo()
        initialLaunch = false
    }

    Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
}

func showUnplayedVideo() {

    // 1. get current video data
    Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

        if !watched {

            // 2. show setup video popup on first load
            guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
            let player = AVPlayer(url: videoURL)

            self.playerVC.player = player

            // 3. fast forward to where user left off (if applicable)
            player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

            // 4. dismiss the player once the video is over and update Firebase
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(self.playerDidFinishPlaying),
                                                   name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                                   object: self.playerVC.player?.currentItem)

            self.present(self.playerVC, animated: true) {
                self.playerVC.player?.play()
            }
        }
    }
}

@objc func playerDidFinishPlaying(note: NSNotification) {
    self.playerVC.dismiss(animated: true)
    Video.updateFirebase(firebaseVideoID: video.firebaseID)
}

Any help would be great. I'm just trying to learn :-)

EDIT #1

This is my attempt at an extension. I simplified and refactored my code, but as before, it's giving me an error. This time the error is 'extensions must not contain stored properties'. So how do I access the AVPlayerController?!?

extension UIViewController {

let playerVC = AVPlayerViewController()

func showUnplayedVideo(video: Video) {

    // 1. get current video data
    Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

        if !watched {
            // 2. show setup video popup on first load
            guard let videoURL = URL(string: video.url) else { print("url error"); return }
            let player = AVPlayer(url: videoURL)

            self.playerVC.player = player

            // 3. fast forward to where user left off (if applicable)
            player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

            // 4. dismiss the player once the video is over and update Firebase
            NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                                   object: playerVC.player?.currentItem,
                                                   queue: .main) { (notification) in

                                                    self.playerDidFinishPlaying(note: notification as NSNotification)

            self.present(self.playerVC, animated: true) {
                self.playerVC.player?.play()
            }
        }
    }
}

    func playerDidFinishPlaying(note: NSNotification, video: Video) {
        self.playerVC.dismiss(animated: true)
        Video.updateFirebase(firebaseVideoID: video.firebaseID)
    }
}

EDIT #2

So I got the code to compile without any errors, but now it's not firing. Aargh.

extension UIViewController {

func showUnplayedVideo(playerVC: AVPlayerViewController, video: Video) {

    print("does this code even fire?")

    // 1. get current video data
    Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

        if !watched {
            // 2. show setup video popup on first load
            guard let videoURL = URL(string: video.url) else { print("url error"); return }
            let player = AVPlayer(url: videoURL)

            playerVC.player = player

            // 3. fast forward to where user left off (if applicable)
            player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

            // 4. dismiss the player once the video is over and update Firebase
            NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                                   object: playerVC.player?.currentItem,
                                                   queue: .main) { (notification) in

                                                    self.playerDidFinishPlaying(playerVC: playerVC, note: notification as NSNotification, video: video)

                                                    self.present(playerVC, animated: true) {
                                                        playerVC.player?.play()
                                                    }
            }
        }
    }
}

func playerDidFinishPlaying(playerVC: AVPlayerViewController, note: NSNotification, video: Video) {
    playerVC.dismiss(animated: true)
    Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
}

Why won't this work?


Solution

  • Have you considered using a more traditional inheritance model?

    class VideoPlayingBaseController: : UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    
        if initialLaunch == true {
            showUnplayedVideo()
            initialLaunch = false
        }
    
        Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
    }
    
    func showUnplayedVideo() {
    
        // 1. get current video data
        Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in
    
            if !watched {
    
                // 2. show setup video popup on first load
                guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
                let player = AVPlayer(url: videoURL)
    
                self.playerVC.player = player
    
                // 3. fast forward to where user left off (if applicable)
                player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))
    
                // 4. dismiss the player once the video is over and update Firebase
                NotificationCenter.default.addObserver(self,
                                                       selector: #selector(self.playerDidFinishPlaying),
                                                       name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                                       object: self.playerVC.player?.currentItem)
    
                self.present(self.playerVC, animated: true) {
                    self.playerVC.player?.play()
                }
            }
        }
    }
    
    @objc func playerDidFinishPlaying(note: NSNotification) {
        self.playerVC.dismiss(animated: true)
        Video.updateFirebase(firebaseVideoID: video.firebaseID)
    }
    
    
    }
    

    Then have your classes that use it:

    class Step3JobSummaryVC: VideoPlayingBaseController {
     //more code here
    }