Massive CPU overuse whenever I push ViewController - Swift - Programmatically - swift

I have a high CPU level whenever I push a new ViewController from the navigationController.
I used the Time Profiler tool and it turns out that the issue that is causing this CPU overuse is related to a ViewController I embedded in the rootViewController.
The ViewController I'm talking about is as follows:
class QuoteGeneratorController : UIViewController {
let interactiveShowTextView : UITextView = {
let textView = UITextView()
textView.font = UIFont(name: "avenir-black", size: 35)
textView.textColor = .white
textView.textAlignment = .left
textView.textContainer.maximumNumberOfLines = 3
textView.backgroundColor = .clear
textView.isEditable = false
textView.isSelectable = false
textView.isUserInteractionEnabled = false
return textView
}()
let progressBar : UIProgressView = {
let progressView = UIProgressView(progressViewStyle: .bar)
progressView.trackTintColor = UIColor.clear
progressView.tintColor = UIColor.white
return progressView
}()
let quotesArray = ["Hello", "Hello2", "Hello3"]
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("I'm called")
view.subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = progressBar.frame.height / 2.0
}
}
override func viewDidAppear(_ animated: Bool) {
setUpAnimation()
}
override func viewDidLoad() {
setUpUI()
setUpAnimation()
}
fileprivate func setUpUI(){
view.addSubview(interactiveShowTextView)
view.addSubview(progressBar)
interactiveShowTextView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor)
interactiveShowTextView.heightAnchor.constraint(equalToConstant: 150).isActive = true
progressBar.anchor(top: interactiveShowTextView.bottomAnchor, leading: interactiveShowTextView.leadingAnchor, bottom: nil, trailing: nil, padding: .init(top: 10, left: 0, bottom: 0, right: 0), size: .init(width: 70, height: 9))
}
var iterator : Int = 0
fileprivate func setUpAnimation(){
switch iterator {
case 0:
animateAndIterate()
case 1:
animateAndIterate()
case 2:
animateAndIterate()
default:
self.iterator = 0
self.setUpAnimation()
}
}
fileprivate func animateAndIterate(){
UIView.animate(withDuration: 0.0, animations: {
self.progressBar.layoutIfNeeded()
}, completion: { finished in
self.progressBar.progress = 1.0
self.interactiveShowTextView.text = self.quotesArray[self.iterator]
self.interactiveShowTextView.fadeIn()
UIView.animate(withDuration: 3, delay: 0.0, options: [.curveLinear], animations: {
self.progressBar.layoutIfNeeded()
self.perform(#selector(self.afterAnimation), with: nil, afterDelay: 2.5)
}, completion: { finished in
self.interactiveShowTextView.fadeOut()
self.progressBar.progress = 0
self.iterator = self.iterator + 1
self.setUpAnimation()
})
})
}
#objc func afterAnimation() {
self.interactiveShowTextView.fadeOut()
}
}
I actually don't know what could have caused the issue and since I'm new to time profiler I though that some of you have encountered the same process through development.
In Time Profiler I'm getting this indications:

When using completion blocks it's recommended to use weak references to the result. Otherwise the ARC will count every reference you used inside those block.
Use closures like this:
view.subviews.forEach { [weak self] subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = progressBar.frame.height / 2.0
}
// (...)
fileprivate func animateAndIterate() {
UIView.animate(withDuration: 0.0,
animations: { [weak self] in
self?.progressBar.layoutIfNeeded()
}, completion: { [weak self] finished in
self?.progressBar.progress = 1.0
self?.interactiveShowTextView.text = self.quotesArray[self.iterator]
self?.interactiveShowTextView.fadeIn()
UIView.animate(withDuration: 3,
delay: 0.0,
options: [.curveLinear],
animations: { [weak self] in
self?.progressBar.layoutIfNeeded()
self?.perform(#selector(self.afterAnimation),
with: nil, afterDelay: 2.5)
}, completion: { finished in
self?.interactiveShowTextView.fadeOut()
self?.progressBar.progress = 0
self?.iterator = self.iterator + 1
self?.setUpAnimation()
})
})
}

Related

How to pass a closure dismissing custom alert avoiding retain cycles

I want to create a custom alert via code. This code below is working, but I Need help on some questions about passing data and retain cycles
is it right my usage of [weak self] ?
I'd like to avoid Delegate Pattern, so my plan is to pass an action as handler, this should keep Controller clean and make code more reusable. Is mine a proper solution?
In my mind, a view should not be "auto removing" but its parent should remove it, so I'd like to pass a reference to the parent controller in order to comunicate via completion, but it seems to create a retain circle (deinit never called), so I'm doing this:
self?.removeFromSuperview() //works, but not sure it is right
//self?.parentController.removeFromParent() //never calls deinit
I had a problem passing closure as parameter inside an init with cgrect as parameter. Is there a proper way other than this solution to handle that?
required init(refersTo: UIViewController, comp: #escaping () -> Void) {
myTransmittedCompletion = comp
self.parentController = refersTo
super.init(frame: CGRect.zero)
}
I call my alert this way
#IBAction func centralButton(_ sender: UIButton) {
let alert = MyAlertInCode(refersTo: self, comp: testPerCOmpletion)
self.view.addSubview(alert)
}
func testPerCOmpletion() {
print("completion")
}
my custom class
class MyAlertInCode: UIView {
let myTransmittedCompletion: () -> Void
let parentController: UIViewController
private let myTitleLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.numberOfLines = 0
v.text = "very long long long logn title"
v.textAlignment = .center
v.font = UIFont.systemFont(ofSize: 28, weight: .bold)
return v
}()
private let mySubtTitleLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.numberOfLines = 0
v.text = "very long long long logn title"
v.textAlignment = .center
v.font = UIFont.systemFont(ofSize: 20, weight: .bold)
return v
}()
private let myButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Button", for: .normal)
v.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
v.setTitleColor(.systemBlue, for: .normal)
return v
}()
//white panel of the alert
private let container: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
v.layer.cornerRadius = 24
v.backgroundColor = .purple
return v
}()
private lazy var stack: UIStackView = {
let v = UIStackView(arrangedSubviews: [myTitleLabel, mySubtTitleLabel, myButton])
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.spacing = 10
v.distribution = .fillEqually
v.backgroundColor = .green
return v
}()
required init(refersTo: UIViewController, comp: #escaping () -> Void) {
myTransmittedCompletion = comp
self.parentController = refersTo
super.init(frame: CGRect.zero)
myButton.addTarget(self, action: #selector(methodInsideAlertClass), for: .touchUpInside)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(animateOut)))
self.backgroundColor = UIColor.gray.withAlphaComponent(0.6)
#warning("UIScreen.main.bounds //deprecated in the future, at 14-ott-2022")
// self.frame = UIScreen.main.bounds //deprecated in the future, at 14-ott-2022
guard let windowBoundsFromIOS13 = UIApplication.shared.currentUIWindow()?.rootViewController?.view.bounds else {return}
self.frame = windowBoundsFromIOS13
self.addSubview(container)
NSLayoutConstraint.activate([
container.centerYAnchor.constraint(equalTo: self.centerYAnchor),
container.centerXAnchor.constraint(equalTo: self.centerXAnchor),
container.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7),
container.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.5),
])
container.addSubview(stack)
NSLayoutConstraint.activate([
stack.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.6),
stack.centerYAnchor.constraint(equalTo: container.centerYAnchor),
stack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: container.trailingAnchor),
])
animateIn()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("❌")
}
//MARK: methods
#objc private func methodInsideAlertClass() {
print("methodInsideAlertClass tapped")
}
#objc private func animateOut() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseIn) {
self.container.transform = CGAffineTransform(translationX: 0, y: -self.frame.height)
self.alpha = 0
} completion: { [weak self] isCompleted in
if isCompleted {
self?.myTransmittedCompletion()
self?.removeFromSuperview() // shouldn't be removed by parent view?
// self?.parentController.removeFromParent() //never calls deinit
}
}
}
#objc private func animateIn() {
self.container.transform = CGAffineTransform(translationX: 0, y: -self.frame.height)
self.alpha = 1
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseIn) {
self.container.transform = .identity
self.alpha = 1
}
}
}
since cannot use Windows:
public extension UIApplication {
func currentUIWindow() -> UIWindow? {
let connectedScenes = UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }
let window = connectedScenes.first?
.windows
.first { $0.isKeyWindow }
return window
}
}

Custom alert presenting over empty window instead of previous viewController

As you can see I am setting the presentation style as .overCurrentContext.
extension SingleQuestionViewController: AddResponseDelegate {
func save(response text: String, questionID: Int) {
questionsWrapper.add(newResponse: text, questionID: questionID) { [weak self] successful in
if successful {
self?.responseTV?.safelyReload()
} else {
DispatchQueue.main.async {
let alertViewController = AlertViewController<Any>()
alertViewController.modalPresentationStyle = .overCurrentContext
let contentModel = RegularContentsModel(title: "controllerTitle", message: "message")
let authorizeButtonModel = SimpleButtonModel(title: "yes message", action: {
//action goes here
})
let doNothingButtonModel = SimpleButtonModel(title: "noMsg", action: {
//completion?()
})
alertViewController.styleRegular(regularContentsModel: contentModel,
models: [authorizeButtonModel, doNothingButtonModel])
self?.present(alertViewController, animated: false, completion: nil)
}
}
}
questionsWrapper.goToQuestion(with: questionID)
responseTV?.safelyReload()
}
}
Here is the result:
I don't think this is caused by it being on the background thread, because if I move it to viewDidLoad, then I get the same result:
override func viewDidLoad() {
super.viewDidLoad()
setUpTopBar()
setupSearchBar()
responseTV.showsVerticalScrollIndicator = false
setupArrows()
responseTV.register(SimpleCell.self, forCellReuseIdentifier: SimpleCell.reuseID)
setAccessibility()
let alertViewController = AlertViewController<Any>()
alertViewController.modalPresentationStyle = .overCurrentContext
let contentModel = RegularContentsModel(title: "controllerTitle", message: "message")
let authorizeButtonModel = SimpleButtonModel(title: "yes message", action: {
//action goes here
})
let doNothingButtonModel = SimpleButtonModel(title: "noMsg", action: {
//completion?()
})
alertViewController.styleRegular(regularContentsModel: contentModel,
models: [authorizeButtonModel, doNothingButtonModel])
self.present(alertViewController, animated: false, completion: nil)
}
Here is the implementation of my custom alert.
class AlertViewController<Payload>: AkinVC {
typealias FlagsAction = ([ReportFlag], Payload) -> Void
enum AlertStyle<Payload> {
case flag(FlagsAction)
}
let innerWholeAlertContainer = UIView()
let outerWholeAlertContainer = UIView()
let buttonStack = AlertButtonsStack()
var payload: Payload?
let transitionDuration: TimeInterval = 0.11
let containerWidth: CGFloat = 300
private var contentsView: UIView! {
didSet {
innerWholeAlertContainer.addSubview(contentsView)
contentsView.constraints(firstHorizontal: .distanceToLeading(innerWholeAlertContainer.leadingAnchor, 0),
secondHorizontal: .distanceToTrailing(innerWholeAlertContainer.trailingAnchor, 0),
vertical: .distanceToTop(innerWholeAlertContainer.topAnchor, 0),
secondVertical: .distanceToBottom(buttonStack.topAnchor, 0))
}
}
func styleNoButtons(regularContentsModel: RegularContentsModel) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
setButtonConstraints()
}
func styleAsFlagView(flagsAction: #escaping FlagsAction) {
initialSetup()
let stackView = FlagsStackView()
stackView.flagItemViews = [FlagItemView](ReportFlag.allCases)
contentsView = stackView
buttonStack.buttonModels(
ButtonModel(tekt: "Report", color: .romanceRed, tektColor: .white, action: { [weak stackView] in
guard let selectedFlags = stackView?.flagItemViews?.selectedFlags,
let payload = self.payload else { return }
flagsAction(selectedFlags, payload)
self.dismissAlert()
}),
ButtonModel(tekt: "Cancel", color: .white, tektColor: .black,
borders: BorderModel(color: UIColor.black.withAlphaComponent(0.16), width: 1, edges: [.top]),
action: { [weak self] in
self?.dismissAlert()
})
)
setButtonConstraints()
}
func styleAsOkayAlert(regularContentsModel: RegularContentsModel, action: Action? = nil) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
let okayModel = standardizeButtonsWithDismissAction(models: [SimpleButtonModel(title: "Okay, I got it.", action: action)])
buttonStack.buttonModels(okayModel)
setButtonConstraints()
}
func styleCancelAlert(regularContentsModel: RegularContentsModel, models: SimpleButtonModel...) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
var models = models
models.append(SimpleButtonModel(title: "Cancel"))
let newButtonModels = standardizeButtonsWithDismissAction(models: models)
buttonStack.buttonModels(newButtonModels)
setButtonConstraints()
}
func styleRegular(regularContentsModel: RegularContentsModel, models: SimpleButtonModel...) {
self.styleRegular(regularContentsModel: regularContentsModel, models: models)
}
func styleRegular(regularContentsModel: RegularContentsModel, models: [SimpleButtonModel]) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
let newButtonModels = standardizeButtonsWithDismissAction(models: models)
buttonStack.buttonModels(newButtonModels)
setButtonConstraints()
}
private func standardizeButtonsWithDismissAction(models: [SimpleButtonModel]) -> [ButtonModel] {
var buttonModelsToAdd: [ButtonModel] = []
let count = models.count
for (inde, model) in models.enumerated() {
var borders: [BorderModel] = []
if count > 2 || count == 1 {
borders.append(BorderModel(color: .lightGray, width: 1, edges: [.top]))
} else if count == 2 {
if inde == 0 {
borders.append(BorderModel(color: .lightGray, width: 1, edges: [.top]))
} else if inde == 1 {
borders.append(BorderModel(color: .lightGray, width: 1, edges: [.left, .top]))
}
}
buttonModelsToAdd.append(ButtonModel(tekt: model.title, color: .white, tektColor: .darkGray, borders: borders, action: {
model.action?()
self.dismissAlert()
}))
}
return buttonModelsToAdd
}
func dismissAlert() {
UIView.animate(withDuration: transitionDuration, animations: {
self.view.alpha = 0
}) { (completed) in
self.safelyDissmiss(animated: false)
}
}
fileprivate func initialSetup() {
self.view.alpha = 0
modalPresentationStyle = .currentContext
view.backgroundColor = UIColor.black.withAlphaComponent(0.3)
view.addSubview(outerWholeAlertContainer)
outerWholeAlertContainer.addSubview(innerWholeAlertContainer)
outerWholeAlertContainer.pinToEdges(innerWholeAlertContainer)
innerWholeAlertContainer.backgroundColor = .white
innerWholeAlertContainer.addSubview(buttonStack)
outerWholeAlertContainer.constraints(.horizontal(.centeredHorizontallyWith(view)),
.vertical(.centeredVerticallyTo(view)),
.horizontal(.width(containerWidth)) )
}
func setButtonConstraints() {
buttonStack.constraints(.horizontal(.distanceToLeading(innerWholeAlertContainer.leadingAnchor, 0)),
.horizontal(.distanceToTrailing(innerWholeAlertContainer.trailingAnchor, 0)),
.vertical(.distanceToBottom(innerWholeAlertContainer.bottomAnchor, 0)))
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
outerWholeAlertContainer.layer.applySketchShadow()
innerWholeAlertContainer.roundCorners(constant: 15)
UIView.animate(withDuration: transitionDuration, animations: {
self.view.alpha = 1
})
}
}
Here is what the visual debugger shows:
The view controller:
creates the alert controller;
sets modalPresentationStyle to .overCurrentContext; and
and calls styleRegular.
But styleRegular:
calls initialSetup
which resets modalPresentationStyle to .currentContext
It's that last step that is discarding your previous .overCurrentContext setting.

In my case, you can star and change stop shimmer animation?

I have a case of such a want to do so after refresh a couple of seconds were loaded with shimmer and finished animation.
my code starAnimatiom :
func startAnimation() {
for animateView in getSubViewsForAnimate() {
animateView.clipsToBounds = true
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.white.withAlphaComponent(0.8).cgColor, UIColor.clear.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.7, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.8)
gradientLayer.frame = animateView.bounds
animateView.layer.mask = gradientLayer
let animation = CABasicAnimation(keyPath: "transform.translation.x")
animation.duration = 1.5
animation.fromValue = -animateView.frame.size.width
animation.toValue = animateView.frame.size.width
animation.repeatCount = .infinity
gradientLayer.add(animation, forKey: "")
}
}
func getSubViewsForAnimate() -> [UIView] {
var obj: [UIView] = []
for objView in view.subviewsRecursive() {
obj.append(objView)
}
return obj.filter({ (obj) -> Bool in
obj.shimmerAnimation
})
}
My code function stopAnimation;
#objc func stopAnimation() {
for animateView in getSubViewsForAnimate() {
animateView.layer.removeAllAnimations()
animateView.layer.mask = nil
timerShimmer.invalidate()
refresh.endRefreshing()
}
}
When I pull down and do the update the animation continues to act and for some reason does not stop.What did I do wrong?
#objc func obnova() {
self.startAnimation()
self.tableView.reloadData()
self.loadObjects1()
self.loadObjects2()
self.loadObjects3()
// self.refresh.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
timerShimmer = Timer.init(timeInterval: 0.2, target: self, selector: #selector(stopAnimation), userInfo: nil, repeats: true)
}
Help me Please?
Change the last section like below and try
func startTimer() {
if timerShimmer != nil {
timerShimmer.invalidate()
timerShimmer = nil
}
timerShimmer = Timer.init(timeInterval: 0.2, target: self, selector: #selector(stopAnimation), userInfo: nil, repeats: true)
}
#objc func obnova() {
self.startAnimation()
self.tableView.reloadData()
self.loadObjects1()
self.loadObjects2()
self.loadObjects3()
startTimer()
}
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}

Warning: Attempt to present error when trying to present UIAlertController on Custom

Warning: Attempt to present on
whose view is not in the window
hierarchy!
I have a custom presented view controller and would like to present UIAlertController on top of it. When presenting without custom transition everything works fine.
Tried adding definesPresentationContext = true without luck
Maybe my custom transition should include something like addChildViewController() ?
First VC
let adVC = AdViewController(with: adView)
adVC.setupAd(with: index)
let adNav = UINavigationController(rootViewController: adVC)
adNav.modalPresentationStyle = .custom
adNav.transitioningDelegate = adVC.adCustomTransition
self.present(adNav, animated: true, completion: nil)
AdViewController
class AdViewController: UIViewController {
var adCustomTransition = AdCustomTransition()
override func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true;
submitButton.addTarget(self, action: #selector(presentAlert), for: .touchUpInside)
}
func presentAlert() {
DispatchQueue.main.async {
let alertVC = UIAlertController(title: "Alert!", message: "test alert message", preferredStyle: .alert)
alertVC.view.tintColor = .main
alertVC.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
self.present(alertVC, animated: true, completion: nil)
}
}
}
class AdCustomTransition : NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AdAnimationController(withDuration: 0.4, forTransitionType: .present)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AdAnimationController(withDuration: 0.4, forTransitionType: .dismiss)
}
}
class AdAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case present, dismiss
}
var duration: TimeInterval
var isPresenting: Bool
init(withDuration duration: TimeInterval, forTransitionType type: TransitionType) {
self.duration = duration
self.isPresenting = type == .present
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!)
if isPresenting == true {
let toVC = (transitionContext.viewController(forKey: .to) as! MainNavigationController).viewControllers.first as! AdViewController
containerView.addSubview(toVC.view)
containerView.layoutIfNeeded()
let bottomHeight = toVC.getSafeBottomHeight()
let offset = toVC.containerView.bounds.height - 120 - bottomHeight
toVC.containerView.transform = CGAffineTransform(translationX: 0, y: offset)
UIView.animate(withDuration: self.duration, delay: 0, usingSpringWithDamping: 5.4, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {
toVC.containerView.transform = CGAffineTransform(translationX: 0, y: -1 * bottomHeight)
}, completion: { (_) in
toVC.view.frame = toFrame
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
} else {
let fromVC = (transitionContext.viewController(forKey: .from) as! MainNavigationController).viewControllers.first as! AdViewController
let toVC = transitionContext.viewController(forKey: .to)!
containerView.addSubview(toVC.view)
containerView.addSubview(fromVC.view)
containerView.layoutIfNeeded()
let bottomHeight = fromVC.getSafeBottomHeight()
let offset = fromVC.containerView.bounds.height - 110 - bottomHeight
fromVC.containerView.transform = .identity
UIView.animate(withDuration: self.duration, delay: 0, usingSpringWithDamping: 5.7, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {
fromVC.containerView.transform = CGAffineTransform(translationX: 0, y: offset)
}, completion: { (_) in
toVC.view.frame = toFrame
UIApplication.shared.keyWindow?.addSubview(toVC.view)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
}

How to remove a custom playerView from superView?

I have a videPlayerView which has ContainerView on top of it to show activityIndicatorView. The view gets loaded when i select an item from a collectionView inside a cell. My question is how to remove this view using the cancelButton, i tried removeFromSuperview() for both the container and the playerView but the app crashes with this error
AQDefaultDevice (1): skipping input stream 0 0 0x0
Here is the code:
class VideoPlayerView: UIView {
var videoUrl: String!
var uuidd: String!
let activityIndicatorView: UIActivityIndicatorView = {
let aiv = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
aiv.translatesAutoresizingMaskIntoConstraints = false
aiv.startAnimating()
return aiv
}()
lazy var controlsContainerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0, alpha: 1)
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handlezoomout)))
return view
}()
func handlezoomout(hh: UITapGestureRecognizer){
print("n3me")
}
lazy var cancelButton: UIButton = {
let cancelButton = UIButton()
cancelButton.setImage(#imageLiteral(resourceName: "cancel"), for: UIControlState())
cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside)
return cancelButton
}()
func cancel() {
controlsContainerView.removeFromSuperview()
let video = VideoPlayerView()
video.removeFromSuperview()
}
lazy var pausePlayButton: UIButton = {
let button = UIButton(type: .system)
let image = UIImage(named: "pause")
button.setImage(image, for: UIControlState())
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = .white
button.isHidden = true
button.addTarget(self, action: #selector(handlePause), for: .touchUpInside)
return button
}()
var isPlaying = false
func handlePause() {
if isPlaying {
player?.pause()
pausePlayButton.setImage(UIImage(named: "play"), for: UIControlState())
} else {
player?.play()
pausePlayButton.setImage(UIImage(named: "pause"), for: UIControlState())
}
isPlaying = !isPlaying
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpPlayerView()
controlsContainerView.frame = frame
addSubview(controlsContainerView)
cancelButton.frame = CGRect(x: 16.0, y: 20.0, width: 30.0, height: 30.0)
controlsContainerView.addSubview(cancelButton)
controlsContainerView.addSubview(activityIndicatorView)
activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
controlsContainerView.addSubview(pausePlayButton)
pausePlayButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
pausePlayButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
pausePlayButton.widthAnchor.constraint(equalToConstant: 50).isActive = true
pausePlayButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
backgroundColor = UIColor.black
}
var player: AVPlayer?
fileprivate func setUpPlayerView() {
let postQuery = PFQuery(className: "posts")
postQuery.whereKey("uuid", equalTo: PostUuidGlobalVariable.postuuid.last!)
postQuery.getFirstObjectInBackground { (object, error) in
if (error == nil && object != nil) {
let videoFile = object!["video"] as! PFFile
if let url = URL (string: videoFile.url!) {
self.player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: self.player)
self.layer.addSublayer(playerLayer)
playerLayer.frame = self.frame
self.player?.play()
self.player?.isMuted = false
self.player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.playerDidFinishPlaying(note:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem)
}
}
}
}
func playerDidFinishPlaying(note: NSNotification) {
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
//this is when the player is ready and rendering frames
if keyPath == "currentItem.loadedTimeRanges" {
activityIndicatorView.stopAnimating()
pausePlayButton.isHidden = false
isPlaying = true
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class VideoLauncher: NSObject {
func showVideoPlayer() {
if let keyWindow = UIApplication.shared.keyWindow {
let view = UIView(frame: keyWindow.frame)
view.isUserInteractionEnabled = true
view.backgroundColor = UIColor.white
view.frame = CGRect(x: keyWindow.frame.width - 10, y: keyWindow.frame.height - 10, width: 10, height: 10)
let height = keyWindow.frame.height
let videoPlayerFrame = CGRect(x: 0, y: 0, width: keyWindow.frame.width, height: height)
let videoPlayerView = VideoPlayerView(frame: videoPlayerFrame)
view.addSubview(videoPlayerView)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
view.frame = keyWindow.frame
}, completion: { (completedAnimation) in
//maybe we'll do something here later...
UIApplication.shared.isStatusBarHidden = true
})
keyWindow.addSubview(view)
}
}
}
There is a chance this is related to you changing the user interface outside of the main thread.
From UIView documents
'Threading Considerations
Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the
UIView
class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself, but all other manipulations should occur on the main thread.'
Also your cancel function creates a new video player view and then tries to remove it from it parent which doesn't look correct.
Your cancel callback should probably be as follows
func cancel() {
DispatchQueue.main.async { [unowned self] in
// to remove controls
self.controlsContainerView.removeFromSuperview()
// to remove video player view
self.view.removeFromSuperview()
}
}