#ios #uicollectionview #uicollectionviewcell #avplayer #swift5
Вопрос:
У меня есть настройка UICollectionView, в которой есть ячейки видеозаписей из моей базы данных. Прямо сейчас, когда загружено представление коллекции, начинают воспроизводиться все видео в разных ячейках. Я хочу, чтобы видео не воспроизводилось ни в каких ячейках, кроме выбранной ячейки, чтобы видео-аудиозаписи не воспроизводились друг над другом. Как я могу это сделать? Вот код…
Контроллер представления:
import UIKit import Photos struct VideoModel { let username: String let videoFileURL: String } class BetaClipsViewController: UIViewController, UICollectionViewDelegate { private var collectionView: UICollectionView? private var data = [VideoModel]() /// Notification observer private var observer: NSObjectProtocol? /// All post models private var allClips: [(clip: Clip, owner: String)] = [] private var viewModels = [[ClipFeedCellType]]() override func viewDidLoad() { super.viewDidLoad() title = "" // for _ in 0..lt;10 { // let model = VideoModel(username: "@CJMJM", // videoFileURL: "https://firebasestorage.googleapis.com:443/v0/b/globe-e8b7f.appspot.com/o/clipvideos/1637024382.mp4?alt=mediaamp;token=c12d0481-f834-4a17-8eee-30595bdf0e8b") // data.append(model) // } let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: view.frame.size.width, height: view.frame.size.height) layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) layout.minimumInteritemSpacing = 0 layout.minimumLineSpacing = 0 collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView?.register(ClipsCollectionViewCell.self, forCellWithReuseIdentifier: ClipsCollectionViewCell.identifier) collectionView?.isPagingEnabled = true collectionView?.delegate = self collectionView?.dataSource = self view.addSubview(collectionView!) fetchClips() observer = NotificationCenter.default.addObserver( forName: .didPostNotification, object: nil, queue: .main ) { [weak self] _ in self?.viewModels.removeAll() self?.fetchClips() } self.collectionView?.reloadData() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView?.frame = view.bounds } private func fetchClips() { // guard let username = UserDefaults.standard.string(forKey: "username") else { // return // } let userGroup = DispatchGroup() userGroup.enter() var allClips: [(clip: Clip, owner: String)] = [] DatabaseManager.shared.clips() { result in DispatchQueue.main.async { defer { userGroup.leave() } switch result { case .success(let clips): allClips.append(contentsOf: clips.compactMap({ (clip: $0, owner: $0.owner) })) case .failure: break } } } userGroup.notify(queue: .main) { let group = DispatchGroup() self.allClips = allClips allClips.forEach { model in group.enter() self.createViewModel( model: model.clip, username: model.owner, completion: { success in defer { group.leave() } if !success { print("failed to create VM") } } ) } group.notify(queue: .main) { self.sortData() self.collectionView?.reloadData() } } } private func sortData() { allClips = allClips.shuffled() viewModels = viewModels.shuffled() } } extension BetaClipsViewController: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -gt; Int { return viewModels.count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -gt; Int { return viewModels[section].count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -gt; UICollectionViewCell { let cellType = viewModels[indexPath.section][indexPath.row] switch cellType { case .clip(let viewModel): guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ClipsCollectionViewCell.identifier, for: indexPath) as? ClipsCollectionViewCell else { fatalError() } cell.delegate = self cell.configure(with: viewModel) return cell } } } extension BetaClipsViewController: ClipsCollectionViewCellDelegate { func didTapProfile(with model: VideoModel) { print("profile tapped") let owner = model.username DatabaseManager.shared.findUser(username: owner) { [weak self] user in DispatchQueue.main.async { guard let user = user else { return } let vc = ProfileViewController(user: user) self?.navigationController?.pushViewController(vc, animated: true) } } } func didTapShare(with model: VideoModel) { print("tapped share") } func didTapNewClip(with model: VideoModel) { let vc = RecordViewController() navigationController?.pushViewController(vc, animated: true) } } extension BetaClipsViewController { func createViewModel( model: Clip, username: String, completion: @escaping (Bool) -gt; Void ) { // StorageManager.shared.profilePictureURL(for: username) { [weak self] profilePictureURL in // guard let clipURL = URL(string: model.clipUrlString), // let profilePhotoUrl = profilePictureURL else { // return // } let clipData: [ClipFeedCellType] = [ .clip(viewModel: VideoModel(username: username, videoFileURL: model.clipUrlString)) ] self.viewModels.append(clipData) completion(true) // } } }
В камере:
import UIKit import AVFoundation protocol ClipsCollectionViewCellDelegate: AnyObject { func didTapProfile(with model: VideoModel) func didTapShare(with model: VideoModel) func didTapNewClip(with model: VideoModel) } class ClipsCollectionViewCell: UICollectionViewCell { static let identifier = "ClipsCollectionViewCell" var playerLooper: NSObject? // Labels private let usernameLabel: UILabel = { let label = UILabel() label.textAlignment = .center label.textColor = UIColor.systemPink.withAlphaComponent(0.5) label.backgroundColor = UIColor.systemPink.withAlphaComponent(0.1) label.clipsToBounds = true label.layer.cornerRadius = 8 return label }() // Buttons private let profileButton: UIButton = { let button = UIButton() button.setBackgroundImage(UIImage(systemName: "person.circle"), for: .normal) button.tintColor = .white button.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.1) button.clipsToBounds = true button.layer.cornerRadius = 32 button.isUserInteractionEnabled = true return button }() private let shareButton: UIButton = { let button = UIButton() button.setBackgroundImage(UIImage(systemName: "square.and.arrow.down"), for: .normal) button.tintColor = .white button.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.1) button.clipsToBounds = true button.layer.cornerRadius = 4 button.isUserInteractionEnabled = true return button }() private let newClipButton: UIButton = { let button = UIButton() button.setBackgroundImage(UIImage(systemName: "plus"), for: .normal) button.tintColor = .systemOrange button.backgroundColor = UIColor.systemOrange.withAlphaComponent(0.1) button.clipsToBounds = true button.layer.cornerRadius = 25 button.isUserInteractionEnabled = true return button }() private let videoContainer = UIView() // Delegate weak var delegate: ClipsCollectionViewCellDelegate? // Subviews var player: AVPlayer? private var model: VideoModel? override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .black contentView.clipsToBounds = true addSubviews() } private func addSubviews() { contentView.addSubview(videoContainer) contentView.addSubview(usernameLabel) contentView.addSubview(profileButton) contentView.addSubview(shareButton) contentView.addSubview(newClipButton) // Add actions profileButton.addTarget(self, action: #selector(didTapProfileButton), for: .touchUpInside) shareButton.addTarget(self, action: #selector(didTapShareButton), for: .touchUpInside) newClipButton.addTarget(self, action: #selector(didTapNewClipButton), for: .touchUpInside) videoContainer.clipsToBounds = true contentView.sendSubviewToBack(videoContainer) } @objc private func didTapProfileButton() { guard let model = model else { return } delegate?.didTapProfile(with: model) } @objc private func didTapShareButton() { guard let model = model else { return } delegate?.didTapShare(with: model) } @objc private func didTapNewClipButton() { guard let model = model else { return } delegate?.didTapNewClip(with: model) } override func layoutSubviews() { super.layoutSubviews() videoContainer.frame = contentView.bounds let size = contentView.frame.size.width/7 let width = contentView.frame.size.width let height = contentView.frame.size.height // Labels usernameLabel.frame = CGRect(x: (width-(size*3))/2, y: height-880-(size/2), width: size*3, height: size) // Buttons profileButton.frame = CGRect(x: width-(size*7), y: height-850-size, width: size, height: size) shareButton.frame = CGRect(x: width-size, y: height-850-size, width: size, height: size) newClipButton.frame = CGRect(x: width-size-10, y: height-175-size, width: size/1.25, height: size/1.25) } override func prepareForReuse() { super.prepareForReuse() usernameLabel.text = nil player?.pause() player?.seek(to: CMTime.zero) } public func configure(with model: VideoModel) { self.model = model configureVideo() // Labels usernameLabel.text = "@" model.username } private func configureVideo() { guard let model = model else { return } guard let url = URL(string: model.videoFileURL) else { return } player = AVPlayer(url: url) let playerView = AVPlayerLayer() playerView.player = player playerView.frame = contentView.bounds playerView.videoGravity = .resizeAspectFill videoContainer.layer.addSublayer(playerView) player?.volume = 5 player?.play() player?.actionAtItemEnd = .none NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd(notification:)), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem) } @objc func playerItemDidReachEnd(notification: Notification) { if let playerItem = notification.object as? AVPlayerItem { playerItem.seek(to: .zero, completionHandler: nil) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }