Let me know if i have made any mistake don't downcast the question.
here is the problem i am trying to play a video and following is code
import UIKit
import AVFoundation
// custom UIView to display frame for avplayer
class VideoPlayerView: UIView {
let urlString String?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blackColor()
//static url for video
urlString = "https://firebasestorage.googleapis.com/v0/b/gameofchats-762ca.appspot.com/o/message_movies%2F12323439-9729-4941-BA07-2BAE970967C7.mov?alt=media&token=3e37a093-3bc8-410f-84d3-38332af9c726"
if let url = NSURL(string: urlString) {
let player = AVPlayer(URL: url)
let playerLayer = AVPlayerLayer(player: player)
self.layer.addSublayer(playerLayer)
playerLayer.frame = self.frame
player.play()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// view controller to launch video player
class VideoPlayerController: UIViewController {
var message: Messages?{
didSet{
}
}
let overlayView: UIView = {
let view = UIView()
view.isUserInteractionEnabled = true
view.backgroundColor = UIColor(white: 0, alpha: 0.5)
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
let height = view.frame.width * 9 / 16
let videoPlayerFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: height)
let videoPlayerView = VideoContainerView(frame: videoPlayerFrame)
view.addSubview(videoPlayerView)
videoPlayerView.center = view.center
}
in the init method of VideoPlayerView i have used static url of video for urlString property which is playing fine. i want to set urlString property from my videoPlayerController class how i can do that ? because when i initialize
let view = VideoPlayerView(frame:frame) // this code will call init method before i can set the urlString property.
anyone have idea how to do that. please help. thank you in advance for any suggestions.
Just make other initializer with needed parameters and call it.
convenience init(frame: CGRect, urlString: String) {
self.init(frame: frame)
//make here what you want
}
Related
I am new to Swift and I'm trying to reproduce the behavior most applications have when you zoom in an image (like Twitter, Facebook etc...). Basically, you tap on the image, it opens fullscreen and then you can zoom in and move freely around the zoomed in image. However, I'm having trouble with the last two parts.
My main ViewController has an instance of a custom UIImageView called TappableImage, which inherits from UIImageView. I use this class as a wrapper to handle touch and transitions, code is below:
import UIKit
class TappableImage: UIImageView {
private let windowBounds: CGRect = UIScreen.main.bounds
private let imageV: UIImageView = UIImageView()
private var startingFrame: CGRect = .zero
private var backdrop: ImageBackdrop!
private var isOpen: Bool = false
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
image = UIImage(named: "kaws") // horizontal
// image = UIImage(named: "portrait") // vertical
// image = UIImage(named: "ksg") // square
isUserInteractionEnabled = true
startingFrame = frame
imageV.image = self.image
imageV.frame = startingFrame
imageV.contentMode = .scaleAspectFill
imageV.isUserInteractionEnabled = true
backdrop = ImageBackdrop(frame: windowBounds, with: imageV)
let openTapGesture = UITapGestureRecognizer(target: self, action: #selector(onTap))
openTapGesture.numberOfTapsRequired = 1
addGestureRecognizer(openTapGesture)
}
#objc private func onTap(_ sender: UITapGestureRecognizer) {
guard let window = UIApplication.shared.windows.first else {
print("no window...")
return
}
window.addSubview(backdrop)
UIView.animate(withDuration: 0.25, animations: {
self.backdrop.alpha = 1
let h = (self.windowBounds.width / self.startingFrame.width) * self.startingFrame.height
let y = self.windowBounds.height / 2 - h / 2
self.imageV.frame = CGRect(x: 0, y: y, width: self.windowBounds.width, height: h)
}) { (_) in
self.isOpen = true
}
}
}
When a user tap the image, I bring a custom UIScrollView to the front in which I added my TappableImage. The UIScrollView is a basic implementation just handling pinching, code is below:
import UIKit
class ImageBackdrop: UIScrollView, UIScrollViewDelegate {
private var imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
convenience init(frame: CGRect, with imgv: UIImageView) {
self.init(frame: frame)
imageView = imgv
setupScrollView()
addSubview(imageView)
}
private func setupScrollView() -> Void {
delegate = self
backgroundColor = .black
alpha = 0
minimumZoomScale = 1.0
maximumZoomScale = 5.0
contentSize = imageView.bounds.size
autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? { imageView }
}
My issue is that when I zoom in, the image is shifted down and I cannot move freely around with one finger. If I try to move to the bottom of the image, I am "slingshotted" back to the top, like when you arrive at the end of a list, you can find a video of this behavior here.
My guess is that the UIImageView center or size is incorrect but I cannot find a way to fix it.
I am working on a chatbot where the different type of response comes from the server and I display the response using UICollectionView cells in chat screen. Different type of cells presents according to server response. when server response with playing video, I am presenting the cell that contains youtube player. I am using https://github.com/kieuquangloc147/YouTubePlayer-Swift. The issue is when I scroll chat screen (collectionView) youtube player is opening again and again. Sometimes it is blocking all the UI element and stop scrolling. I tried different methods but can't able to resolve it. Here is the code:
PlayerView:
import UIKit
class PlayerView: UIView, YouTubePlayerDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
addYotubePlayer()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// youtube player
lazy var youtubePlayer: YouTubePlayerView = {
let viewFrame = UIScreen.main.bounds
let player = YouTubePlayerView(frame: CGRect(x: 0, y: 0, width: viewFrame.width - 16, height: viewFrame.height * 1/3))
player.delegate = self
return player
}()
// used as an overlay to dismiss the youtube player
let blackView = UIView()
// youtube player loader
lazy var playerIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView()
indicator.activityIndicatorViewStyle = .whiteLarge
indicator.hidesWhenStopped = true
return indicator
}()
// shows youtube player
func addYotubePlayer() {
if let window = UIApplication.shared.keyWindow {
blackView.frame = window.frame
self.addSubview(blackView)
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tap.numberOfTapsRequired = 1
tap.cancelsTouchesInView = false
blackView.addGestureRecognizer(tap)
let centerX = UIScreen.main.bounds.size.width / 2
let centerY = UIScreen.main.bounds.size.height / 2
blackView.addSubview(playerIndicator)
playerIndicator.center = CGPoint(x: centerX, y: centerY)
playerIndicator.startAnimating()
blackView.addSubview(youtubePlayer)
youtubePlayer.center = CGPoint(x: centerX, y: centerY)
blackView.alpha = 0
youtubePlayer.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.youtubePlayer.alpha = 1
}, completion: nil)
}
}
func play(_ videoID: String) {
youtubePlayer.loadVideoID(videoID)
}
#objc func handleDismiss() {
blackView.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
UIApplication.shared.keyWindow?.removeFromSuperview()
}
func playerReady(_ videoPlayer: YouTubePlayerView) {
self.playerIndicator.stopAnimating()
}
func playerStateChanged(_ videoPlayer: YouTubePlayerView, playerState: YouTubePlayerState) {
}
func playerQualityChanged(_ videoPlayer: YouTubePlayerView, playbackQuality: YouTubePlaybackQuality) {
}
}
YouTubePlayerCell (Which I present in collectionView wthe hen server responds for video):
import UIKit
class YouTubePlayerCell: ChatMessageCell {
var player: PlayerView = PlayerView(frame: UIScreen.main.bounds)
override func setupViews() {
super.setupViews()
setupCell()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect.zero
}
func loadVideo(with videoID: String) {
player.tag = 24
UIApplication.shared.keyWindow?.addSubview(player)
player.play(videoID)
}
override func prepareForReuse() {
super.prepareForReuse()
player.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
}
}
Here is how I am presenting the YouTubePlayerCell in cellForItemAt method of UICollectionView
let message = messages[indexPath.row]
if message.actionType == ActionType.video_play.rawValue {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.youtubePlayerCell, for: indexPath) as? YouTubePlayerCell {
self.resignResponders()
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
return cell
}
}
Full Source Code can be found here: https://github.com/imjog/susi_iOS/tree/ytplayer
I can see that in the below code
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
you are calling loadVideo method, which is responsible for showing the player.
So while scrolling you are reusing the cell and it calls loadVideo method and present the player. so the solution is don't start playing the video by default on displaying the cell, provide a play/pause button on the cell video overlay and on clicking the the button start playing the video.
If my analysis is wrong please let me know, what exact issue you have.
Why do you add the player as a subView each time you have to play the video ? My suggestion would be, as you are adding the player view on the whole screen, you can have just one instance of the view and add it just once(may be at the beginning) and keep it hidden. To play the video just unhide the player and load the video.
Instead best practice would be to have a View controller for Youtube Player and present it with the video id each time you need to play and then dismissing when done.
Thanks for your answers. I solve this by this way:
Rather than presenting Player on setting on the cell, I am now adding a thumbnail to the cell and a button on thumbnail view so that whenever the user clicks play button, it opens a new controller (Previously I was presenting in UIWindow) and presenting it as modalPresentationStyle of overFullScreen by using protocol because cell cannot present a ViewController.
Protocol: (In YouTubePlayerCell class)
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
Final YouTubePlayer.swift:
import UIKit
import Kingfisher
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
class YouTubePlayerCell: ChatMessageCell {
weak var delegate: PresentControllerDelegate?
var message: Message? {
didSet {
addThumbnail()
}
}
lazy var thumbnailView: UIImageView = {
let imageView = UIImageView()
imageView.image = ControllerConstants.Images.placeholder
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 15
imageView.isUserInteractionEnabled = true
return imageView
}()
lazy var playButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(ControllerConstants.Images.youtubePlayButton, for: .normal)
button.addTarget(self, action: #selector(playVideo), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func setupViews() {
super.setupViews()
setupCell()
prepareForReuse()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect(x: 8, y: 0, width: 208, height: 158)
textBubbleView.layer.borderWidth = 0.2
textBubbleView.backgroundColor = .white
}
override func prepareForReuse() {
super.prepareForReuse()
thumbnailView.image = nil
}
func addThumbnail() {
textBubbleView.addSubview(thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "H:|-4-[v0]-4-|", views: thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "V:|-4-[v0]-4-|", views: thumbnailView)
self.downloadThumbnail()
self.addPlayButton()
}
func addPlayButton() {
thumbnailView.addSubview(playButton)
playButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
playButton.widthAnchor.constraint(equalToConstant: 44).isActive = true
playButton.centerXAnchor.constraint(equalTo: thumbnailView.centerXAnchor).isActive = true
playButton.centerYAnchor.constraint(equalTo: thumbnailView.centerYAnchor).isActive = true
}
func downloadThumbnail() {
if let videoID = message?.videoData?.identifier {
let thumbnailURLString = "https://img.youtube.com/vi/\(videoID)/default.jpg"
let thumbnailURL = URL(string: thumbnailURLString)
thumbnailView.kf.setImage(with: thumbnailURL, placeholder: ControllerConstants.Images.placeholder, options: nil, progressBlock: nil, completionHandler: nil)
}
}
#objc func playVideo() {
if let videoID = message?.videoData?.identifier {
let playerVC = PlayerViewController(videoID: videoID)
playerVC.modalPresentationStyle = .overFullScreen
delegate?.loadNewScreen(controller: playerVC)
}
}
}
Delegate implementation in CollectionViewController:
extension ChatViewController: PresentControllerDelegate {
func loadNewScreen(controller: UIViewController) {
self.present(controller, animated: true, completion: nil)
}
}
Final source code can be found here: https://github.com/fossasia/susi_iOS/pull/372
I have some requirement to show popup as some customisation options.
So, I took custom view as UIView with Xib file.
class CustomAlertView : UIView {
#IBOutlet weak var customAlertView : UIView!
#IBOutlet weak var button1 : UIButton!
#IBOutlet weak var button2 : UIButton!
override init(frame: CGRect) { // for using CustomView in code
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) { // for using CustomView in IB
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("CustomAlertView", owner: self)
guard let content = customAlertView else { return }
content.frame = self.bounds
content.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.addSubview(content)
}
}
And given outlet connections.
And I am able to load the view in viewcontroller class.
But, the issue is, its not showing as pop up view and its not showing any border color, etc even I added too.
And the present self.view (Main view from viewcontroller) still moving it has tableview, while I clicking on the buttons on custom view, nothing happening.
func someAction() {
self.view.addSubview(customAlert)
self.customAlert.layer.cornerRadius = 13
self.customAlert.layer.borderWidth = 10
self.customAlert.layer.borderColor = UIColor.gray.cgColor
self.customAlert.clipsToBounds = false
let radius: CGFloat = self.customAlert.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: self.customAlert.frame.height))
self.customAlert.layer.masksToBounds = false
self.customAlert.layer.cornerRadius = 8; // if you like rounded corners
self.customAlert.layer.shadowOffset = CGSize(width:-15, height:20);
self.customAlert.layer.shadowRadius = 5;
self.customAlert.layer.shadowOpacity = 0.5;
self.customAlert.layer.shadowPath = shadowPath.cgPath
self.customAlert.byMonthlyBtn.addTarget(self, action: #selector(button1tapped), for: .touchUpInside)
self.customAlert.byAnnuallyBtn.addTarget(self, action: #selector(button2tapped), for: .touchUpInside)
}
And its looks like below screenshot
Any suggestions?
private func commonInit() {
Bundle.main.loadNibNamed("CustomAlertView", owner: self)
guard let content = customAlertView else { return }
content.frame = self.bounds
content.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.addSubview(content)
}
Your problem is you are using the CustomAlertView you loaded. Try adjust your code like below.
private func commonInit() {
guard let view = Bundle.main.loadNibNamed("CustomAlertView", owner: self)?.first as? CustomAlertView else {
return
}
view.frame = bounds
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(view)
}
I am implmenting pages control, and in each page video will be playing, but i had made it with using xibs, load each video in each xib, only the first video is being get played, rest all videos not getting played, showing as blank screen.
Main Code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
loadScrollView()
}
func loadScrollView() {
let pageCount : CGFloat = 3.0
scrollView.backgroundColor = UIColor.clear
scrollView.delegate = self
scrollView.isPagingEnabled = true
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * pageCount, height: scrollView.frame.size.height)
scrollView.showsHorizontalScrollIndicator = false
pageController.numberOfPages = Int(pageCount)
let v1 = Video1.instanceFromNib() // xib with video 1
let v2 = Video2.instanceFromNib() // xib with video 2
let v3 = Video3.instanceFromNib() // xib with video 3
var videoClassArray = [v1,v2,v3] as [UIView]
for i in 0..<Int(pageCount) {
print(self.scrollView.frame.size.width)
var view = UIView(frame: CGRect(x: self.scrollView.frame.size.width * CGFloat(i), y: 0, width: self.scrollView.frame.size.width, height: self.scrollView.frame.size.height))
view = videoClassArray[i]
self.scrollView.addSubview(view)
}
}
//MARK: UIScrollView Delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewWidth: CGFloat = scrollView.frame.size.width
// content offset - tells by how much the scroll view has scrolled.
let pageNumber = floor((scrollView.contentOffset.x - viewWidth / 50) / viewWidth) + 1
pageController.currentPage = Int(pageNumber)
}
//MARK: Page tap action
func pageChanged() {
let pageNumber = pageController.currentPage
var frame = scrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageNumber)
frame.origin.y = 0
scrollView.scrollRectToVisible(frame, animated: true)
}
The above is the view controller where im loading the xibs into various views.And the xib code is in the below.
class Video1: UIView {
class func instanceFromNib() -> UIView {
return UINib(nibName: "Video1", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
var player: AVPlayer!
var playerLayer = AVPlayerLayer()
var videoName = "BoardSetup_1"
#IBOutlet weak var baseView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
let videoURL = Bundle.main.url(forResource: videoName, withExtension: "mp4")
player = AVPlayer(url: videoURL!)
player?.actionAtItemEnd = .none
player?.isMuted = true
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
playerLayer.zPosition = -1
playerLayer.bounds = self.layer.bounds
playerLayer.frame = baseView.frame
baseView.layer.addSublayer(playerLayer)
baseView.backgroundColor = UIColor.blue
player?.play()
//loop video
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.player?.seek(to: kCMTimeZero)
self.player.play()
}
})
}
}
The above code is for xib with loading the videos , the code is same for the rest xibs, so mentioning only 1 xib.
The issue im facing while loading the xib only the video will be get played in first view and in rest view its showing as blank screen.
Why not use should use UIPageViewController for pagination? It's possible to use UIScrollView but then you need to implement the page recycling yourself otherwise you end up wasting a lot of memory.
I'm trying to initialise an MPMoviePlayerController in a custom UIView. I do hear the sound but no view. I think something is wrong with initialising the view. I tried it by implementing in the ViewController, then it does work.
Here my code:
import UIKit
import MediaPlayer
class CustomView: UIView {
var sponsor: Sponsors!
var videoContainer = UIView()
var videoPlayer = MPMoviePlayerController()
override init(frame: CGRect) {
super.init(frame: frame)
//Init
layer.cornerRadius = 3.0
backgroundColor = UIColor.whiteColor()
//VideoContainer
videoContainer.backgroundColor = UIColor.blackColor()
//Adding the views
addSubview(videoContainer)
videoContainer.addSubview(videoPlayer.view)
}
//MARK: InitCoder
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
//MARK: AddDataToView
func addDataToView(sponsor: Sponsors) {
//VideoPlayerx
let path = NSBundle.mainBundle().pathForResource("movie5", ofType:"mov")
let url = NSURL.fileURLWithPath(path!)
videoPlayer = MPMoviePlayerController(contentURL: url)
videoPlayer.prepareToPlay()
}
//MARK: LayoutSubViews
override func layoutSubviews() {
super.layoutSubviews()
//VideoContainer
videoContainer.frame = CGRectMake(8, 8, frame.size.width - 16, 219)
//Video
videoPlayer.view.frame = CGRectMake(8, 8, frame.size.width - 16, 219)
videoPlayer.scalingMode = .AspectFill
You need to set frame and add subview after create videoPlayer object. In addDataToView you init videoPlayer again so every settings before that are cleared.