AVPlayerViewController and AVPictureInPictureController - swift

I have PIP working using AVPlayer, AVPlayerViewController
By default, when entering back into the app/view from background with PIP active, I'd like it to return back into the view.
My issue is that when I setup the AVPictureInPictureController using my AVPlayerLayer, I can get back pip observations however, none of the pipController methods like stopPictureInPicture() or isPictureInPictureActive work or return false.
How can I get both AVPlayerViewController and AVPictureInPictureController to work together?
if AVPictureInPictureController.isPictureInPictureSupported() {
// Create a new controller, passing the reference to the AVPlayerLayer.
DispatchQueue.main.async {
videoController.pipController = AVPictureInPictureController(playerLayer: videoController.playerLayer)
videoController.pipController!.delegate = videoController
videoController.pipPossibleObservation = videoController.pipController!.observe(\AVPictureInPictureController.isPictureInPicturePossible,
options: [.initial, .new]) { _, change in
// Update the PiP button's enabled state.
print("Can pip")
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
print("Stop Pip")
print("\(videoController.pipController!.isPictureInPictureActive)") // Returns false
}
}
}
} else {
// PiP isn't supported by the current device. Disable the PiP button.
print("Can not pip")
}

Related

tvos 15 - Siri Remote not returning in the array: GCController.controllers()

in swiftUI & tvOS 15, when calling the GCController.controllers() to get the list of controllers connected to the apple tv,
import GameController
...
let siriRemoteAsGameController = GCController.controllers().first
the Siri Remote is not registered as the first controller, in fact it is not registered at all !
up until tvOS 15 (14.7 for example) it was working
even if i register for notification the connect event isn't dispatched for the already connected Siri remote
NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .main) { note in
print("GCControllerDidConnect")
if let detectedGCController = note.object as? GCController {
print("Controller Detected")
}
}
GCController.startWirelessControllerDiscovery(completionHandler: {})
i cannot find a change in that area according to Appel's $#itty documentation
any help would be appreciated
based on this answer it seems that an interaction with the remote after calling (at least once) to GCController.controllers() is required so the solution was this:
import GameController
struct ContentView2: View {
var body: some View {
// first call before remote interaction
let a = print("controllers: \(GCController.controllers())")
Button("Query controllers") {
// second call after a press button via remote interaction occurred
print("controllers: \(GCController.controllers())")
}
}
}

How to fix performance issue while loading new animation in Lottie?

I am trying to make a simple game with Xcode 11.2 which contains an animated loop background and a view which shows and changes various animations from several Lottie JSON files in project.
When I click the "Next" button to change the view animation, background view which is looping gets stuck for a second until the next animation inside the view is loaded an everything in the app freezes at the same time just like the background animation.
CPU usage varies from 30% to 63%.
I don't like to complicate the question, so I am just showing the way I used Lottie.
#IBAction func SubmitButtonAction(_ sender: UIButton) {
showNextQuestion()
}
func showNextQuestion()->(){
myTimer.invalidate()
startCountdown(fromTime: 15)
Manager.generateQuestion()
lblLevel.text = String(Manager.questionNumber) + "/" + String(DataModel.Questions.count)
nIndex = 0
let animation = AnimationView(name: Manager.currentImage)
animation.loopMode = .loop
animation.play()
animation.backgroundColor = UIColor.clear
animation.frame = self.AnimView.bounds
animation.backgroundBehavior = .pauseAndRestore
if AnimView.subviews.isEmpty{
self.AnimView.addSubview(animation)
}
else {
for one in imgImageView.subviews{
one.removeFromSuperview()
}
self.AnimView.addSubview(animation)
}
AnswerCollectionView.reloadData()
RandomCollectionView.reloadData()
}
I am interested in suggestions as to what the problem is related to - could it be related to a threading issue?
In response to Jaseel.Dev, I created a function that returned a LottieView with the following:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation(.spring()) {
LottieView(name: animation)
}
}

Using UserDefaults in viewWillAppear

I'm setting up the Settings page (in SettingsView class) where users can set «On» / «Off» for the background parallax effect. The selection is saved in UserDefaults().string(forKey: "parallaxStatus"). In the viewWillAppear of ViewController class, I checked the parallaxStatus. If the status of the parallax effect is «On», then this effect is displayed. If the status is «Off», then nothing should happen.
The problem appeared when parallaxStatus changed from «On» to «Off» In this case, the parallax effect still displayed before I reload the View. But if parallaxStatus changed from «Off» to «On», the function works well without reloading the View.
Bellow is the code of viewWillAppear function. Thanks for any help or hint.
override func viewWillAppear(_ animated: Bool) {
let parallaxStatus = UserDefaults().string(forKey: "parallaxStatus")
if parallaxStatus == "On" {
let min = CGFloat(-40)
let max = CGFloat(40)
let xMotion = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.x", type: .tiltAlongHorizontalAxis)
xMotion.minimumRelativeValue = min
xMotion.maximumRelativeValue = max
let yMotion = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.y", type: .tiltAlongVerticalAxis)
yMotion.minimumRelativeValue = min
yMotion.maximumRelativeValue = max
let motionEffectGroup = UIMotionEffectGroup()
motionEffectGroup.motionEffects = [xMotion,yMotion]
bgImage.addMotionEffect(motionEffectGroup) } else { }
}
1- You should use a bool value in userDefaults
UserDefaults.standard.bool(forKey: "parallaxStatusOn") // default is false
2- viewWillAppear is called when you dimiss a presented / poped vc so in your case , you use a settingsView not vc verify it's being called by other KVO or any event driven notifier
3- if the state is on and changed to off , verify you remove the motion effects with if the vc is still appeared ( not deallocated btw do it in else of check )
bgImage.motionEffects.forEach { bgImage.removeMotionEffect($0) }

Swift Dynamic animation followed by property animator

I have an animation where I use a push animation, then a snap animation using UIDynamicBehavior, and then I finish with a property behavior:
for card in selectedCards {
removeCard(card: card)
}
private func removeCard(card: Card) {
guard let subView = cardsContainer.subviews.first(where: { ($0 as? PlayingCardView)?.card == card }) else {
return
}
if let card = subView as? PlayingCardView { card.selected = false }
let matchedCardsFrame = matchedCards.convert(matchedCards.frame, to: view)
view.addSubview(subView)
cardBehavior.addItem(subView) // here I add the push behavior
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.cardBehavior.removeItem(subView) // here I remove the push behavior
UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 0.3,
delay: 0,
options: [],
animations: {
self.cardBehavior.addSnapBehavior(subView, frame: matchedCardsFrame) // here I add the snap behavior
}, completion: { finished in
self.animator.removeAllBehaviors()
subView.frame.size = CGSize(width: matchedCardsFrame.height, height: matchedCardsFrame.width)
subView.transform = CGAffineTransform.identity.rotated(by: CGFloat.pi / 2)
subView.setNeedsDisplay()
})
}
}
Essentially the above code does the following:
Add push behavior
Remove push behavior
Add snap behavior
Remove all behaviors
Add property transform
What I want is for the push action to execute, then after a second or so, have the snap behavior execute, and after the snap execution is finished, to perform a transform. However, if I removeAllBehaviors() before I execute the property transform then the snap behavior doesn't finish. But if I leave the snap behavior and try to execute the property transform then it has no effect since it appears that the snap behavior acts on the object indefinitely, putting it at odds with the property transform.
How can I programmatically say finish the snap behavior and then perform the transform?

App changes to different controller view when changing orientation

I'm developing an app using Xcode 9.2 and swift 4 and I needed to allow just one view in my app to change to landscape, so I added the code below to my AppDelegate
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if globalVariables.gIsDosageView == "Y" {
if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
return UIInterfaceOrientationMask.all;
} else {
return UIInterfaceOrientationMask.portrait;
}
} else {
return UIInterfaceOrientationMask.portrait;
}
}
The global variable is set to "N" on every other controller and so only DosageView has it set to "Y". In the DosageView controller I added this method to help with the transition.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
hideViews()
if size.width < 400 {
DispatchQueue.main.async() {
self.userImg.isHidden = false
self.burgerImg.isHidden = false
self.deviceImg.isHidden = false
self.portraitView.isHidden = false
self.landscapeView.isHidden = true
}
}
else {
DispatchQueue.main.async() {
self.userImg.isHidden = true
self.burgerImg.isHidden = true
self.deviceImg.isHidden = true
self.portraitView.isHidden = true
self.landscapeView.isHidden = false
self.loadLandscapeView()
}
}
}
I've set up a LandscapeView and a PortraitView and the method above helps to toggle between them and it all works fine. If I move to the next view or return to the main screen and then return to the DosageView and then change the orientation to Landscape it works but if I go to the next view and then from there to the Connect view and connect (via BLE, NFC or QR) it then returns to the DosageView. When I change the orientation to Landscape it changes back to the Connect view. There is no direct link between these two views as you have to go through the Instruction view to get to Connect View for the DosageView, so how can this happen?
When I run it under debug and put a breakpoint in the ViewDidLoad on Connect View, it runs every time the DosageView changes to Landscape. If anyone can tell me what is going on I would be grateful as this is driving me crazy.
It was actually a colleague who sorted this for me and as he correctly pointed out it was a Lottie run call causing the problem. For anyone using Lottie and experiencing this problem, simply check your animation code. I had originally plagiarised code from a Splash screen as below
splashAnimation!.loopAnimation = false
splashAnimation!.play(fromProgress: 0,
toProgress: 1.0,
withCompletion: { (finished) in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "Menu")
self.present(controller, animated: true, completion: nil)
})
This code works fine as it moves on to the main screen on completion. However, the screen that was causing the problems moves between two different screens and to fix the problem, my colleague simply removed the withCompletion part as below
splashAnimation!.loopAnimation = true
splashAnimation!.play(fromProgress: 0,
toProgress: 1.0)