SwiftUI - AVPlayerViewController Full Screen on tvOS - swift

I am able to present an AVPlayerViewController from SwiftUI but there is some padding around the video and I would like for it to be full-screen.
From the SwiftUI portion there is the following:
var body: some View {
NavigationView {
List {
ForEach(topicsArray) { topic in
Section(header: Text(topic.title)) {
ForEach(0..<topic.shows.count) { index in
NavigationLink(destination: PlayerView(showID: topic.shows[index])) {
ShowCell(showID: topic.shows[index])
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
}
.listStyle(GroupedListStyle())
.padding()
}.onAppear(perform: initialDataLoad)
}
The code being called from the NavigationLink that shows the player is:
struct PlayerView: UIViewControllerRepresentable {
var showID:Int
func makeUIViewController(context: Context) -> AVPlayerViewController {
let pv = PlayerViewController()
pv.showID = showID
return pv
}
func updateUIViewController(_ viewController: AVPlayerViewController, context: Context) {
}
}
class PlayerViewController: AVPlayerViewController {
var showID:Int! {
didSet {
setup()
}
}
private var videoLaunch:VideoLaunch!
private func setup() {
videoLaunch = VideoLaunch(showID: showID,
season: nil,
episodeID: nil,
selectedIndex: IndexPath(row: 0, section: 0),
showType: .single,
dataStructure: topics as Any,
screenType: .live)
playVideo()
}
private func playVideo() {
guard let videoURL = self.videoLaunch.getMediaURL() else {print("Problem getting media URL");return}
self.player = AVPlayer(url: videoURL)
self.videoGravity = .resizeAspectFill
self.player?.play()
}
I have tried setting the bounds and using the modalpresentationstyle for fullscreen, but none have had any impact. There is still what looks like a 10 point border around the video.

I was able to solve the issue by inserting the following within the PlayerViewController class.
override func viewDidLayoutSubviews() {
self.view.bounds = UIScreen.main.bounds
}

Related

Can I control Reality Composer behaviors in RealityKit?

I would like to make a button using SwiftUI. When the button is pressed, the model will hide. I have already read the tutorial in this link (Creating a Trigger), but I don't know how to control it programmatically.
Here is my code:
struct VocabView : View {
#State private var arView = ARView(frame: .zero)
var body: some View {
ZStack{
ARViewContainer(arView: $arView)
.ignoresSafeArea()
VStack {
Button("hide") {
hide()
}
}
}
}
func hide() {
let demoScene = try! Experience1.loadDemo()
if arView.scene.anchors.count > 0 {
if arView.scene.anchors[0].isAnchored {
demoScene.notifications.hide.post()
}
}
}
}
struct ARViewContainer2: UIViewRepresentable {
#Binding var arView: ARView
func makeUIView(context: Context) -> ARView {
let demoScene = try! Experience1.loadDemo()
DispatchQueue.main.async {
arView.scene.anchors.append(demoScene)
}
return arView
}
}
Here is the configuration in Reality Composer:
You are loading your model twice – at first in makeUIView() method and secondly in hide() method. Try my version.
import SwiftUI
import RealityKit
struct ContentView : View {
#State private var arView = ARView(frame: .zero)
#State private var scene = try! Experience.loadBox()
var body: some View {
ZStack{
ARViewContainer(arView: $arView, scene: $scene)
.ignoresSafeArea()
VStack {
Spacer()
Button("Hide Model") { hideModel() }
}
}
}
private func hideModel() {
if arView.scene.anchors.count > 0 {
if arView.scene.anchors[0].isAnchored {
scene.notifications.notify.post()
}
}
}
}
struct ARViewContainer : UIViewRepresentable {
#Binding var arView: ARView
#Binding var scene: Experience.Box
func makeUIView(context: Context) -> ARView {
arView.scene.anchors.append(scene)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}

Can I use .playAudio() to resume playback after stopping?

From the question in this If I assign a sound in Reality Composer, can I stop it programmatically in RealityKit?, I would like to use method to resume playback after Play Music.
Can I do that?
Now, I use this command in stopAudio function to stop the music.
func stopAudio() {
if arView.scene.anchors.count > 0 {
if arView.scene.anchors[0].isAnchored {
arView.scene.anchors[0].children[0].stopAllAudio()
}
}
}
If I want arView.scene.anchors[0] to replay the music again, which command should I use?
Audio Playback Controller
Since RealityKit 2.0 isn't able to control parameters of Reality Composer's behaviors, the best strategy for controlling audio is to create a programmatic AudioPlaybackController. To feed your audio file to the controller, export .rcproject scene to .usdz format and use unzipping trick to extract the .aiff, .caf or .mp3 sound file. When loading audio for playback, you can choose between spatial and non-spatial audio experience.
UIKit version
import UIKit
import RealityKit
extension ViewController {
private func loadAudio() {
do {
let resource = try AudioFileResource.load(
named: "planetarium07.caf",
in: nil,
inputMode: .spatial,
loadingStrategy: .preload,
shouldLoop: true)
self.controller = entity.prepareAudio(resource)
self.controller?.speed = 0.9
self.controller?.fade(to: .infinity, duration: 2)
} catch {
print(error.localizedDescription)
}
}
}
ViewController.
class ViewController : UIViewController {
#IBOutlet var uiView: UIView! // when using #IBAction buttons
#IBOutlet var arView: ARView!
private var entity = Entity()
private var controller: AudioPlaybackController? = nil
override func viewDidLoad() {
super.viewDidLoad()
uiView.backgroundColor = .systemCyan
let boxScene = try! Experience.loadBox()
arView.scene.anchors.append(boxScene)
let anchor = boxScene.anchor
anchor?.addChild(entity)
self.loadAudio()
}
#IBAction func playMusic(_ sender: UIButton) {
self.controller?.play()
}
#IBAction func stopMusic(_ sender: UIButton) {
self.controller?.pause()
// self.controller?.stop()
}
}
SwiftUI version
import SwiftUI
import RealityKit
struct ContentView : View {
#State var arView = ARView(frame: .zero)
#State var controller: AudioPlaybackController? = nil
#State var entity = Entity()
var body: some View {
ZStack {
ARViewContainer(arView: $arView,
entity: $entity).ignoresSafeArea()
VStack {
Spacer()
Button("Play") { loadSound(); controller?.play() }
Button("Stop") { controller?.stop() }
}
}
}
func loadSound() {
do {
let resource = try AudioFileResource.load(
named: "planetarium07.caf",
in: nil,
inputMode: .spatial,
loadingStrategy: .preload,
shouldLoop: true)
self.controller = entity.prepareAudio(resource)
} catch {
print(error.localizedDescription)
}
}
}
ARViewContainer.
struct ARViewContainer: UIViewRepresentable {
#Binding var arView: ARView
#Binding var entity: Entity
func makeUIView(context: Context) -> ARView {
let boxScene = try! Experience.loadBox()
arView.scene.anchors.append(boxScene)
let anchor = boxScene.anchor
anchor?.addChild(entity)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}

How to open a SwiftUI view by tapping an entity in RealityKit?

I am using SwiftUI with RealityKit. As displayed in the code below, I have a plane entity that when tapped simply prints the name of the entity. What approach should I take toward navigating to a new view when I tap the entity? It would be preferable to navigate as with a navigation link in a normal view, but if that is not possible then perhaps a fullScreenCover?
ARViewContainer.swift:
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
print(entity.name)
}
}
}
struct ARViewContainer: UIViewRepresentable {
typealias UIViewType = ARView
func makeUIView(context: Context) -> ARView{
let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)
context.coordinator.view = arView
arView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
arView.scene.anchors.removeAll()
let anchor = AnchorEntity()
let plane = MeshResource.generatePlane(width: 1, height: 1)
var material = UnlitMaterial()
material.color = .init(tint: .white,
texture: .init(try! .load(named: "instagram")))
let planeEntity = ModelEntity(mesh: plane, materials: [material])
planeEntity.generateCollisionShapes(recursive: true)
planeEntity.name = "Plane Entity"
planeEntity.position.z -= 1.0
planeEntity.setParent(anchor)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context){
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
ContentView.swift
struct ContentView: View {
#State var open = false
var body: some View {
NavigationView{
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
}
}
}
}
View I want to navigate to:
struct TestView : View {
var body : some View {
VStack{
Text("Test View")
}
}
}
Manage the state of the view in an observable object and modify it from your AR view.
struct ContentView: View {
#ObservedObject var settings = Settings.shared
var body: some View {
NavigationView {
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
NavigationLink("", isActive: $settings.shouldOpenDetailsView) {
TestView()
}
}
}
}
}
class Settings: ObservableObject {
static let shared = Settings()
#Published var shouldOpenDetailsView = false
}
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
Settings.shared.shouldOpenDetailsView = true
}
}
}

SwiftUI pass parameter to Class from UIViewRepresentable

I have a question that I think is quite simple and yet I get stuck, so I come to ask for help and maybe it could be other people !
I want to loop a video, but i've problem, i need to pass a var to a struct then a class like :
struct FinalPreview: View {
var url: URL
PlayerView(url: url)
.aspectRatio(contentMode: .fill)
.frame(width: size.width, height: size.height)
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
.onAppear{
if player.currentItem == nil {
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
player.play()
})
}
}
struct PlayerView: UIViewRepresentable {
var url: URL
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
// Load the resource
let fileUrl = "MY URL VAR FROM PlayerView"
// Setup the player
let player = AVPlayer(url: fileUrl)
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill
layer.addSublayer(playerLayer)
// Setup looping
player.actionAtItemEnd = .none
// Start the movie
player.play()
}
#objc
func playerItemDidReachEnd(notification: Notification) {
playerLayer.player?.seek(to: CMTime.zero)
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
It's to loop a video.
I'm also looking to hide AVPlayer controls by using :
struct AVPlayerControllerRepresented: UIViewControllerRepresentable {
var player: AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
}
}
To hide the controls, it works very well, I don't know how to combine both...
Thank you.
You actually don't need 3 different types, just a single UIViewControllerRepresentable with a url:
import AVKit
import SwiftUI
struct PlayerView: UIViewControllerRepresentable {
var url: URL
func makeUIViewController(context: Context) -> AVPlayerViewController {
// create a looping auto-starting video player:
let player = AVPlayer(url: url)
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { [weak player] _ in
player?.seek(to: .zero)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak player] in
player?.play()
}
// use that player in a view controller:
let controller = AVPlayerViewController()
controller.player = player
controller.videoGravity = .resizeAspectFill
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {}
}
struct PlayerView_Previews: PreviewProvider {
static var previews: some View {
PlayerView(url: someVideoURL) // replace the URL with your own
}
}

SwiftUI: Is it possible to let the user scale an image chosen with PHpicker?

I have an image picker created with PHPicker, and I was wondering if it is possible to let the user scale the chosen image?
This is not the entire code, but just the code for the makeUIViewController which I think is what is needed to solve this problem. I can of course provide the rest of the code if necessary.
This is what I'm looking for
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images
config.selectionLimit = 1
let controller = PHPickerViewController(configuration: config)
controller.delegate = context.coordinator
return controller
}
can use this one line after choose the image to fixed height and width of your image
Image(room.thumbnailImage)
.resizable()
.frame(width: 32.0, height: 32.0)
or here i am sharing my running work with you checkout function didFinishPicking and var body: some View
import SwiftUI
import PhotosUI
struct PhotoPickerDemo: View {
#State private var isPresented: Bool = false
#State var pickerResult: [UIImage] = []
var config: PHPickerConfiguration {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images //videos, livePhotos...
config.selectionLimit = 0 //0 => any, set 1-2-3 for har limit
return config
}
var body: some View {
ScrollView {
LazyVStack {
Button("Present Picker") {
isPresented.toggle()
}.sheet(isPresented: $isPresented) {
PhotoPicker(configuration: self.config,
pickerResult: $pickerResult,
isPresented: $isPresented)
}
ForEach(pickerResult, id: \.self) { image in
Image.init(uiImage: image)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 250, alignment: .center)
.aspectRatio(contentMode: .fit)
}
}
}
}
}
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.parent.pickerResult.append(newImage as! UIImage)
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}
struct photoPickerDemo_Previews: PreviewProvider {
static var previews: some View {
PhotoPickerDemo()
}
}
or if you want to crop via user interface like attach picture
Step 1
Using Xcode 12, go to File -> Swift Packages -> Add Package Dependency and enter https://github.com/marshallino16/ImageCropper
Step 2
in your didFinishPicking method where you are receiving selected image pass it in this package using these lines
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(yourSelectedImageHere),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
edited use of ImageCropperView
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(newImage),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}