Looping an iOS live photo programmatically in SwiftUI - ios

I'd like to be able to loop a live photo, for continuous playback.
So far, I'm trying to use the PHLivePhotoViewDelegate to accomplish this.
import Foundation
import SwiftUI
import PhotosUI
import iOSShared
struct LiveImageView: UIViewRepresentable {
let view: PHLivePhotoView
let model:LiveImageViewModel?
let delegate = LiveImageLargeMediaDelegate()
init(fileGroupUUID: UUID) {
let view = PHLivePhotoView()
// Without this, in landscape mode, I don't get proper scaling of the image.
view.contentMode = .scaleAspectFit
self.view = view
// Using this to replay live image repeatedly.
view.delegate = delegate
model = LiveImageViewModel(fileGroupUUID: fileGroupUUID)
guard let model = model else {
return
}
model.getLivePhoto(previewImage: nil) { livePhoto in
view.livePhoto = livePhoto
}
}
func makeUIView(context: Context) -> UIView {
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let model = model else {
return
}
guard !model.started else {
return
}
model.started = true
view.startPlayback(with: .full)
}
}
class LiveImageLargeMediaDelegate: NSObject, PHLivePhotoViewDelegate {
func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
livePhotoView.stopPlayback()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) {
livePhotoView.startPlayback(with: .full)
}
}
}
But without full success. It seems the audio does play again, but not the video. The livePhotoView.stopPlayback and the async aspect are just additional changes I was trying. I've tried it without those too.
Note that I don't want the user to have to manually change the live photo (e.g., see NSPredicate to not include Loop and Bounce Live Photos).
Thoughts?

ChrisPrince I tried your code and it works fine for me, I just add delegate and start playback inside of it and everything runs well and smoothly. I thought that there is no point in using stop playback because the function itself says that the playback ended.
func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
livePhotoView.startPlayback(with: .full)
}

Related

Agora.io VideoCanvas gets recycled between UICollectionViewCells

I’ve been using the agora SDK (audio only) for a while now, and I’m very pleased! Users can enter rooms of max 8 people and talk to each other. Now, I’m supposed to add a video feature, so they can enable their video stream at any time they want.
I’ve added the AgoraRtcEngine_iOS 3.7.0 pod and this is how I initialize the agora engine:
agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: AppKeys.agoraAppId, delegate: self)
agoraKit?.setChannelProfile(.liveBroadcasting)
agoraKit?.setClientRole(.broadcaster)
agoraKit?.enableVideo()
agoraKit?.muteLocalVideoStream(true)
agoraKit?.muteLocalAudioStream(true)
agoraKit?.enableAudioVolumeIndication(1000, smooth: 3, report_vad: true)
This way, when a user joins a room, they have muted their audio and video streams, so they can enable it whenever they’re ready. The thing is, I’m using a UICollectionView to present the broadcasters. (everyone's user cell is visible at all times, so no reuse takes place, but collection view reloads happen constantly)
This is part of the cell setup (cellForItemAt), that handles the video view using a delegate:
private func setupVideoView(uid: UInt, isOfCurrentUser: Bool) {
videoView.frame.size = avatarBackgroundView.frame.size
videoView.layer.cornerRadius = avatarBackgroundView.layer.cornerRadius
if isOfCurrentUser {
delegate?.localVideoSetupWasRequested(videoView: videoView)
} else {
delegate?.remoteVideoSetupWasRequested(uid: uid, videoView: videoView)
}
}
And this is the conformance to the protocol:
extension RealTimeAudioAgoraService: NewVoiceRoomCellsDelegate {
func localVideoSetupWasRequested(videoView: UIView) {
guard !enabledVideoUIds.contains(currentUserRTCServiceId) else { return }
let videoCanvas = setUpVideoView(uid: currentUserRTCServiceId, videoView: videoView)
agoraKit?.setupLocalVideo(videoCanvas)
}
func remoteVideoSetupWasRequested(uid: UInt, videoView: UIView) {
guard !enabledVideoUIds.contains(uid) else { return }
let videoCanvas = setUpVideoView(uid: uid, videoView: videoView)
agoraKit?.setupRemoteVideo(videoCanvas)
}
private func setUpVideoView(uid: UInt, videoView: UIView) -> AgoraRtcVideoCanvas {
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = uid
videoCanvas.view = videoView
videoCanvas.renderMode = .hidden
// ------------------------------------
enabledVideoUIds.append(uid)
// ------------------------------------
return videoCanvas
}
}
As you can see, I keep track of all the uids I have enabled the video canvas for, so I do it only once for each user. The thing is something messes up the UI (see attached video) and even users with muted videos show the video canvas of others. It’s like the video canvas of each user is being cycled over every other user.
Any help will be much appreciated!

PHPickerViewController tapping on Search gets error... "Unable to load photos"

I'm trying to implement a PHPickerViewController using SwiftUI and The Composable Architecture. (Not that I think that's particularly relevant but it might explain why some of my code is like it is).
Sample project
I've been playing around with this to try and work it out. I created a little sample Project on GitHub which removes The Composable Architecture and keeps the UI super simple.
https://github.com/oliverfoggin/BrokenImagePickers/tree/main
It looks like iOS 15 is breaking on both the UIImagePickerViewController and the PHPickerViewController. (Which makes sense as they both use the same UI under the hood).
I guess the nest step is to determine if the same error occurs when using them in a UIKit app.
My code
My code is fairly straight forward. It's pretty much just a reimplementation of the same feature that uses UIImagePickerViewController but I wanted to try with the newer APIs.
My code looks like this...
public struct ImagePicker: UIViewControllerRepresentable {
// Vars and setup stuff...
#Environment(\.presentationMode) var presentationMode
let viewStore: ViewStore<ImagePickerState, ImagePickerAction>
public init(store: Store<ImagePickerState, ImagePickerAction>) {
self.viewStore = ViewStore(store)
}
// UIViewControllerRepresentable required functions
public func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> some UIViewController {
// Configuring the PHPickerViewController
var config = PHPickerConfiguration()
config.filter = PHPickerFilter.images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// This is the coordinator that acts as the delegate
public class Coordinator: PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let itemProvider = results.first?.itemProvider,
itemProvider.canLoadObject(ofClass: UIImage.self) else {
return
}
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
if let image = image as? UIImage {
DispatchQueue.main.async {
self?.parent.viewStore.send(.imagePicked(image: image))
}
}
}
}
}
}
All this works in the simple case
I can present the ImagePicker view and select a photo and it's all fine. I can cancel out of it ok. I can even scroll down the huge collection view of images that I have. I can even see the new image appear in my state object and display it within my app. (Note... this is still WIP and so the code is a bit clunky but that's only to get it working initially).
The problem case
The problem is that when I tap on the search bar in the PHPickerView (which is a search bar provided by Apple in the control, I didn't create it or code it). It seems to start to slide up the keyboard and then the view goes blank with a single message in the middle...
Unable to Load Photos
[Try Again]
I also get a strange looking error log. (I removed the time stamps to shorten the lines).
// These happen on immediately presenting the ImagePicker
AppName[587:30596] [Picker] Showing picker unavailable UI (reason: still loading) with error: (null)
AppName[587:30596] Writing analzed variants.
// These happen when tapping the search bar
AppName[587:30867] [lifecycle] [u A95D90FC-C77B-43CC-8FC6-C8E7C81DD22A:m (null)] [com.apple.mobileslideshow.photospicker(1.0)] Connection to plugin interrupted while in use.
AppName[587:31002] [lifecycle] [u A95D90FC-C77B-43CC-8FC6-C8E7C81DD22A:m (null)] [com.apple.mobileslideshow.photospicker(1.0)] Connection to plugin invalidated while in use.
AppName[587:30596] [Picker] Showing picker unavailable UI (reason: crashed) with error: (null)
AppName[587:30596] viewServiceDidTerminateWithError:: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}
Tapping the "Try Again" button reloads the initial scroll screen and I can carry on using it. But tapping the search bar again just shows the same error.
I'm usually the first one to point out that the error is almost definitely not with the Apple APIs but I'm stumped on this one. I'm not sure what it is that I'm doing that is causing this to happen?
Is it the fact that it's in a SwiftUI view?
Recreated the project in UIKit
I remade the same project using UIKit... https://github.com/oliverfoggin/UIKit-Image-Pickers
And I couldn't replicate the crash at all.
Also... if you are taking any sort of screen recording of the device the crash will not happen. I tried taking a recording on the device itself and couldn't replicate it. I also tried doing a movie recording from my Mac using the iPhone screen and couldn't replicate the crash. But... the instant I stopped the recording on QuickTime the crash was replicable again.
This fixed it for me .ignoreSafeArea(.keyboard) like #Frustrated_Student mentions.
To elaborate on #Frustrated_Student this issue has to do with the UIViewControllerRepresentable treating the view like many SwiftUI views to automatically avoid the keyboard. If you are presenting the picker using a sheet as I am then you can simply add the .ignoreSafeArea(.keyboard) to the UIViewControllerRepresentable view in my case I called it ImagePicker here is a better example.
Where to add it the .ignoreSafeArea(.keyboard)
.sheet(isPresented: $imagePicker) {
ImagePicker(store: store)
.ignoresSafeArea(.keyboard)
}
This is #Fogmeister code:
public struct ImagePicker: UIViewControllerRepresentable {
// Vars and setup stuff...
#Environment(\.presentationMode) var presentationMode
let viewStore: ViewStore<ImagePickerState, ImagePickerAction>
public init(store: Store<ImagePickerState, ImagePickerAction>) {
self.viewStore = ViewStore(store)
}
// UIViewControllerRepresentable required functions
public func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> some UIViewController {
// Configuring the PHPickerViewController
var config = PHPickerConfiguration()
config.filter = PHPickerFilter.images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// This is the coordinator that acts as the delegate
public class Coordinator: PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let itemProvider = results.first?.itemProvider,
itemProvider.canLoadObject(ofClass: UIImage.self) else {
return
}
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
if let image = image as? UIImage {
DispatchQueue.main.async {
self?.parent.viewStore.send(.imagePicked(image: image))
}
}
}
}
}
}
Well.. this seems to be an iOS bug.
I have cerated a sample project here that shows the bug... https://github.com/oliverfoggin/BrokenImagePickers
And a replica project here written with UIKit that does not... https://github.com/oliverfoggin/UIKit-Image-Pickers
I tried to take a screen recording of this happening but it appears that if any screen recording is happening (whether on device or via QuickTime on the Mac) this suppresses the bug from happening.
I have filed a radar with Apple and sent them both projects to have a look at and LOTS of detail around what's happening. I'll keep this updated with any progress on that.
Hacky workaround
After a bit of further investigation I found that you can start with SwiftUI and then present a PHPickerViewController without this crash happening.
From SwiftUI if you present a UIViewControllerRepresentable... and then from there if you present the PHPickerViewController it will not crash.
So I came up with a (very tacky) workaround that avoids this crash.
I first create a UIViewController subclass that I use like a wrapper.
class WrappedPhotoPicker: UIViewController {
var picker: PHPickerViewController?
override func viewDidLoad() {
super.viewDidLoad()
if let picker = picker {
present(picker, animated: false)
}
}
}
Then in the SwiftUI View I create this wrapper and set the picker in it.
struct WrappedPickerView: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var photoPickerResult: PHPickerResult?
let wrappedPicker = WrappedPhotoPicker()
func makeUIViewController(context: Context) -> WrappedPhotoPicker {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = 1
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
wrappedPicker.picker = picker
return wrappedPicker
}
func updateUIViewController(_ uiViewController: WrappedPhotoPicker, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: PHPickerViewControllerDelegate {
let parent: WrappedPickerView
init(_ parent: WrappedPickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.presentationMode.wrappedValue.dismiss()
parent.wrappedPicker.dismiss(animated: false)
parent.photoPickerResult = results.first
}
}
}
This is far from ideal as I'm presenting at the wrong time and stuff. But it works until Apple provide a permanent fix for this.
I started getting a weird UI bug after the PHPickerViewController crashed where the keyboard was not visible but my views were still being squashed. So I suspected a keyboard / avoidance issue. I disabled keyboard avoidance in a parent view and managed to stop it from crashing.
.ignoresSafeArea(.keyboard)
.... still a iOS bug in 15.0. I've modified Fogmeister's class Coordinator to return the image in addition to the PHPickerResult.
struct WrappedPickerView: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var photoPickerResult: PHPickerResult?
#Binding var image: UIImage?
let wrappedPicker = WrappedPhotoPicker()
func makeUIViewController(context: Context) -> WrappedPhotoPicker {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = 1
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
wrappedPicker.picker = picker
return wrappedPicker
}
func updateUIViewController(_ uiViewController: WrappedPhotoPicker, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: PHPickerViewControllerDelegate {
let parent: WrappedPickerView
init(_ parent: WrappedPickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
self.parent.presentationMode.wrappedValue.dismiss()
self.parent.wrappedPicker.dismiss(animated: false)
self.parent.photoPickerResult = results.first
print(results)
guard let result = results.first else {
return
}
self.parent.image = nil
DispatchQueue.global().async {
result.itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in
guard let imageLoaded = object as? UIImage else {
return
}
DispatchQueue.main.async {
self.parent.image = imageLoaded
}
}
}
}
}
}

Alternative to animation(forKey:) (now deprecated)?

I am working with iOS 11 (for ARKit) and while many point to a sample app for SceneKit from Apple with a Fox, I am having problem with the extension it uses in that sample project (file) to add animations:
extension CAAnimation {
class func animationWithSceneNamed(_ name: String) -> CAAnimation? {
var animation: CAAnimation?
if let scene = SCNScene(named: name) {
scene.rootNode.enumerateChildNodes({ (child, stop) in
if child.animationKeys.count > 0 {
animation = child.animation(forKey: child.animationKeys.first!)
stop.initialize(to: true)
}
})
}
return animation
}
}
It seems that this extension is very handy but I am not sure how to migrate this now that it is deprecated? Is it built into SceneKit by default now?
The documentation didn't really show much info on why it was deprecated or where to go from here.
Thanks
TL;DR: examples of how to use new APIs can be found in Apple's sample game (search for SCNAnimationPlayer)
Even though animation(forKey:) and its sister methods that work with CAAnimation have been deprecated in iOS11, you can continue using them – everything will work.
But if you want to use new APIs and don't care about backwards compatibility (which you wouldn't need in the case of ARKit anyway, because it's only available since iOS11), read on.
The newly introduced SCNAnimationPlayer provides a more convenient API compared to its predecessors. It is now easier to work with animations in real time.
This video from WWDC2017 would be a good starting point to learn about it.
As a quick summary: SCNAnimationPlayer allows you to change animation's speed on the fly. It provides a more intuitive interface for animation playback using methods such as play() and stop() compared to adding and removing CAAnimations.
You also can blend two animations together which, for example, can be used to make smooth transitions between them.
You can find examples of how to use all of this in the Fox 2 game by Apple.
Here's the extension you've posted adapted to use SCNAnimationPlayer (which you can find in the Character class in the Fox 2 sample project):
extension SCNAnimationPlayer {
class func loadAnimation(fromSceneNamed sceneName: String) -> SCNAnimationPlayer {
let scene = SCNScene( named: sceneName )!
// find top level animation
var animationPlayer: SCNAnimationPlayer! = nil
scene.rootNode.enumerateChildNodes { (child, stop) in
if !child.animationKeys.isEmpty {
animationPlayer = child.animationPlayer(forKey: child.animationKeys[0])
stop.pointee = true
}
}
return animationPlayer
}
}
You can use it as follows:
Load the animation and add it to the corresponding node
let jumpAnimation = SCNAnimationPlayer.loadAnimation(fromSceneNamed: "jump.scn")
jumpAnimation.stop() // stop it for now so that we can use it later when it's appropriate
model.addAnimationPlayer(jumpAnimation, forKey: "jump")
Use it!
model.animationPlayer(forKey: "jump")?.play()
Lësha Turkowski's answer without force unwraps.
extension SCNAnimationPlayer {
class func loadAnimationPlayer(from sceneName: String) -> SCNAnimationPlayer? {
var animationPlayer: SCNAnimationPlayer?
if let scene = SCNScene(named: sceneName) {
scene.rootNode.enumerateChildNodes { (child, stop) in
if !child.animationKeys.isEmpty {
animationPlayer = child.animationPlayer(forKey: child.animationKeys[0])
stop.pointee = true
}
}
}
return animationPlayer
}
}
Here's an example of SwiftUI and SceneKit
import SwiftUI
import SceneKit
struct ScenekitView : UIViewRepresentable {
#Binding var isPlayingAnimation: Bool
let scene = SCNScene(named: "art.scnassets/TestScene.scn")!
func makeUIView(context: Context) -> SCNView {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
controlAnimation(isAnimating: isPlayingAnimation, nodeName: "TestNode", animationName: "TestAnimationName")
}
func controlAnimation(isAnimating: Bool, nodeName: String, animationName: String) {
guard let node = scene.rootNode.childNode(withName: nodeName, recursively: true) else { return }
guard let animationPlayer: SCNAnimationPlayer = node.animationPlayer(forKey: animationName) else { return }
if isAnimating {
print("Play Animation")
animationPlayer.play()
} else {
print("Stop Animation")
animationPlayer.stop()
}
}
}
struct DogAnimation_Previews: PreviewProvider {
static var previews: some View {
ScenekitView(isPlayingAnimation: .constant(true))
}
}
A 2023 example.
I load typical animations like this:
func simpleLoadAnim(filename: String) -> SCNAnimationPlayer {
let s = SCNScene(named: filename)!
let n = s.rootNode.childNodes.filter({!$0.animationKeys.isEmpty}).first!
return n.animationPlayer(forKey: n.animationKeys.first!)!
}
So,
laugh = simpleLoadAnim(filename: "animeLaugh") // animeLaugh.dae
giggle = simpleLoadAnim(filename: "animeGiggle")
You then, step one, have to add them to the character:
sally.addAnimationPlayer(laugh, forKey: "laugh")
sally.addAnimationPlayer(giggle, forKey: "giggle")
very typically you would have only one going at a time. So set the weights, step two.
laugh.blendFactor = 1
giggle.blendFactor = 0
to play or stop an SCNAnimationPlayer it's just step three
laugh.play()
giggle.stop()
Almost certainly, you will have (100s of lines) of your own code to blend between animations (which might take only a short time, 0.1 secs, or may take a second or so). To do so, you would use SCNAction.customAction.
If you prefer you can access the animation, on, the character (sally) using the keys. But really the "whole point" is you can just start, stop, etc etc the SCNAnimationPlayer.
You will also have lots of code to set up the SCNAnimationPlayer how you like (speed, looping, mirrored, etc etc etc etc etc etc etc etc)
You will need THIS very critical answer to get collada files (must be separate character / anim files) working properly https://stackoverflow.com/a/75093081/294884
Once you have the various SCNAnimationPlayer animations working properly, it is quite easy to use, run, blend etc animes.
The essential sequence is
each anime must be in its own .dae file
load each anime files in to a SCNAnimationPlayer
"add" all the animes to the character in question
program the blends
then simply play() or stop() the actual SCNAnimationPlayer items (don't bother using the keys on the character, it's a bit pointless)

how can I handle properly the behavior of camera in my Swift app?

In my swift app I'm allowing users to take photos.
For that purpose I've decided to use CameraManager from here: https://github.com/imaginary-cloud/CameraManager
When user opens my app, he sees a button - when he presses it, the camera view appears and he can take a photo. He also can dismiss the camera view and later on, at some point, press the button one more time to open camera view again.
If I understand it correctly from the plugin docs, I need to add a camera view to my view during first usage, then - in case of dismiss - invoke stopCaptureSession(), and during every next usage call resumeCaptureSession().
Currently in my swift code I have three methods:
let cameraManager = CameraManager()
fileprivate func addCameraToView()
{
cameraManager.addPreviewLayerToView(cameraView, newCameraOutputMode: CameraOutputMode.stillImage)
}
fileprivate func stopCaptureSession() {
cameraManager.stopCaptureSession()
}
fileprivate func resumeCaptreSession() {
cameraManager.resumeCaptureSession()
}
The IBAction for the button has the following code:
let currentCameraState = cameraManager.currentCameraStatus()
if currentCameraState == .notDetermined {
cameraManager.askUserForCameraPermission({ permissionGranted in
if permissionGranted {
self.resumeCaptreSession()
}
})
} else if (currentCameraState == .ready) {
self.resumeCaptreSession()
} else {
print("we do not have access to camera")
}
and in the IBAction for the dismiss button I had:
print("cancelling camera")
stopCaptureSession()
To make it work properly, I need to call addCameraToView() somewhere earlier - until now I was adding it in viewDidLoad, but I realized that I cannot do that because while doing so - the camera stays active until user presses the dismiss button.
So I thought about changing my code in IBAction for the camera button and add a camera from there. However, I have to add it only in case it wasn't add before - in the other case I need to call resumeCaptureSession().
The problem is that in CameraManager the function responsible for adding camera to the view is declared like this:
open func addPreviewLayerToView(_ view: UIView, newCameraOutputMode: CameraOutputMode) -> CameraState {
return addLayerPreviewToView(view, newCameraOutputMode: newCameraOutputMode, completion: nil)
}
open func addLayerPreviewToView(_ view: UIView, newCameraOutputMode: CameraOutputMode, completion: ((Void) -> Void)?) -> CameraState {
if _canLoadCamera() {
if let _ = embeddingView {
if let validPreviewLayer = previewLayer {
validPreviewLayer.removeFromSuperlayer()
}
}
if cameraIsSetup {
_addPreviewLayerToView(view)
cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
} else {
_setupCamera({ Void -> Void in
self._addPreviewLayerToView(view)
self.cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
})
}
}
return _checkIfCameraIsAvailable()
}
and resumeCaptureSession() is defined like this:
open func resumeCaptureSession() {
if let validCaptureSession = captureSession {
if !validCaptureSession.isRunning && cameraIsSetup {
validCaptureSession.startRunning()
_startFollowingDeviceOrientation()
}
} else {
if _canLoadCamera() {
if cameraIsSetup {
stopAndRemoveCaptureSession()
}
_setupCamera({Void -> Void in
if let validEmbeddingView = self.embeddingView {
self._addPreviewLayerToView(validEmbeddingView)
}
self._startFollowingDeviceOrientation()
})
}
}
}
So my question is - when user opens camera view, how can I check if camera was added to the view before, and if it was added - call resumeCaptureSession(), otherwise do not call it and just leave it with calling addCameraToView?

AVPlayer Swift: How do I hide controls and disable landscape view?

since this is my first post, just a few words about me: Usually I design stuff (UI primarily) but I really wanted to take a leap into programming to understand you guys better. So I decided build a small app to get going.
So I've been trying to figure this out for hours now – this is my first app project ever so I apologise for my newbyness.
All I want to do is to hide the controls of AVPlayer and disable landscape view but I just can't figure out where to put showsPlaybackControls = false.
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
private var firstAppear = true
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if firstAppear {
do {
try playVideo()
firstAppear = false
} catch AppError.InvalidResource(let name, let type) {
debugPrint("Could not find resource \(name).\(type)")
} catch {
debugPrint("Generic error")
}
}
}
private func playVideo() throws {
guard let path = NSBundle.mainBundle().pathForResource("video", ofType:"mp4") else {
throw AppError.InvalidResource("video", "m4v")
}
let player = AVPlayer(URL: NSURL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.player = player
self.presentViewController(playerController, animated: false) {
player.play()
}
}
}
enum AppError : ErrorType {
case InvalidResource(String, String)
}
showsPlaybackControls is a property of AVPlayerViewController, so you can set it after you create it:
playerController.showsPlaybackControls = false
If you want to allow only landscape, you can subclass AVPlayerViewController, override supportedInterfaceOrientations and return only landscape, then use that class instead of AVPlayerViewController.
UPDATE
As mentioned in the comments, if you go to the documentation for AVPlayerViewController you'll find a warning that says:
Do not subclass AVPlayerViewController. Overriding this class’s methods is unsupported and results in undefined behavior.
They probably don't want you to override playback-related methods that could interfere with the behavior, and I would say that overriding only supportedInterfaceOrientations is safe.
However, if you want an alternative solution, you can create your own view controller class that overrides supportedInterfaceOrientations to return landscape only, and place the AVPlayerViewController inside that view controller.

Resources