How to show my AVPlayer in a VStack with SwiftUI - avplayer

I have created a simple AVPlayer. (If I didn't create it correctly, please help me fixing it... 😀 ) I want to display it in my VStack with swift ui, but I'm kind of stuck...
If at least there was the AVPlayer View component in the library, it would have been easier, but I didn't found it in the library... 😔
Here is my code in the ContentView.swift:
//
// ContentView.swift
// test
//
// Created by Francis Dolbec on 2019-06-26.
// Copyright © 2019 Francis Dolbec. All rights reserved.
//
import SwiftUI
import AVKit
import AVFoundation
// MARK: variables
var hauteurMenuBar = NSApplication.shared.mainMenu?.menuBarHeight
var urlVideo = URL(string: "/Users/francisdolbec/Movies/Séries Télé/Rick et Morty/Rick.and.Morty.S01E01.VFQ.HDTV.1080p.x264-Kamek.mp4")
let player = AVPlayer(url: urlVideo!)
struct ContentView : View {
var body: some View {
VStack {
Text("Hello World")
.frame(maxWidth: .infinity, maxHeight: .infinity)
Text("PATATE!")
//player.play()
}
.frame(minWidth: 1024, idealWidth: 1440, maxWidth: .infinity, minHeight: (640-hauteurMenuBar!), idealHeight: (900-hauteurMenuBar!), maxHeight: .infinity)
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
By the way, if there's a tutorial that show how to make a video player with swift ui, I would really appreciate it!
EDIT
I forgot to say that I am developing a macOS app.

Sure, here's the tutorial, by Chris Mash on Medium.
Basically you embed an AVPlayerLayer into a struct conforming to UIViewRepresentable, something you'll do for any UIView component you want to use with SwiftUI.
struct PlayerView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
The "meat" of the implementation is done in the PlayerUIView class:
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
override init(frame: CGRect) {
super.init(frame: frame)
let url = URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!
let player = AVPlayer(url: url)
player.play()
playerLayer.player = player
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
And then you use it like this:
var body: some View {
PlayerView()
}

Thanks to Bogdan for the quick answer and for the like to the tutorial!
Here is the code, converted to NSView so it could work for macOS apps...
Here is the struct PlayerView :
struct PlayerView: NSViewRepresentable {
func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<PlayerView>) {
}
func makeNSView(context: Context) -> NSView {
return PlayerNSView(frame: .zero)
}
}
Here is the class, "the meat" like Bogdan said:
class PlayerNSView: NSView{
private let playerLayer = AVPlayerLayer()
override init(frame:CGRect){
super.init(frame: frame)
let urlVideo = URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!
let player = AVPlayer(url: urlVideo)
player.play()
playerLayer.player = player
if layer == nil{
layer = CALayer()
}
layer?.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layout() {
super.layout()
playerLayer.frame = bounds
}
}
Finally you can use PlayerView() to add the AVPlayer in the struct ContentView.

Related

How can I solve "No exact matches in call to initializer" error when I am using Text in SwiftUI?

I just started with SwiftUI and have basic questions.
I try to show the value of the rotation in the Text field. The error message I get is: "No exact matches in call to initializer"
Where is the mistake?
import SwiftUI
struct ContentView: View {
#State var rotation: Double = 0
var body: some View {
VStack {
VStack {
Text("Hello, world!")
.padding()
Slider(value: $rotation, in: 0 ... 360, step: 0.1)
.padding()
Text(rotation)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and for fun you can also try this:
struct ContentView: View {
#State var rotation: Double = 0
var body: some View {
VStack {
Text("Hello, world!").rotationEffect(Angle(degrees: rotation)).padding()
Slider(value: $rotation, in: 0 ... 360, step: 0.1).padding()
Text("\(rotation)")
}
}
}
You should return a String type for initialization of Text, like this:
struct ContentView: View {
#State var rotation: Double = 0
var body: some View {
VStack {
VStack {
Text("Hello, world!")
.padding()
Slider(value: $rotation, in: 0 ... 360, step: 0.1)
.padding()
Text(rotation.string)
}
}
}
}
extension CustomStringConvertible { var string: String { get { return String(describing: self) } } }

SwiftUI handling SpriteKit scene recreation when data changes

I have a SwiftUI view that is connected to a CoreData model. I also have a SpriteKit scene that changes data in my model. So every time I manipulate my data from my SKScene in CoreData my scene gets reinitialised which is an unwanted behaviour in my case.
How can I get the updated model in my SpriteView without the SKScene being recreated?
My code looks like this:
struct TamagotchiListView: View {
#Environment(\.managedObjectContext)
var context: NSManagedObjectContext
#FetchRequest(fetchRequest: TamagotchiModel.getFetchRequest())
var tamagotchis: FetchedResults<TamagotchiModel>
var body: some View {
VStack {
List {
ForEach(tamagotchis, id: \.self) { (tamagotchi: TamagotchiModel) in
NavigationLink(destination: SpriteKitView(scene: SpriteKitScene(model: tamagotchi))) {
HStack {
Image(systemName: "gamecontroller")
.padding(.trailing, 5)
VStack(alignment: .leading) {
Text(tamagotchi.name)
.font(.headline)
Spacer()
Text(tamagotchi.birthDate, style: .date)
}
Spacer()
}
}
}
}
}
}
I managed to work around my problem by creating a view model that manages the SpriteKit scene creation if needed.
class TamagotchiViewModel {
private var spriteKitScenes: [SpriteKitScene] = []
func scene(for tamagotchi: TamagotchiModel) -> SpriteKitScene {
if let scene = spriteKitScenes.first(where: { $0.tamagotchi?.tamagotchiModel.id == tamagotchi.id}) {
return scene
} else {
let newScene = SpriteKitScene(model: tamagotchi)
spriteKitScenes.append(newScene)
return newScene
}
}
}

OnAppear calls unexpectedly when Keyboard Appears in SwiftUI

I am experiencing very odd behavior in SwiftUI 2.0 and iOS14.
When the keyboard appears on the screen, the OnAppear method of other tab's view called automatically.
However, this works fine Xcode 11.7
Here is the issue in action.
Here is the code which produces the above error.
struct ContentView: View {
var body: some View {
TabView {
DemoView(screenName: "Home")
.tabItem {
Image.init(systemName: "star.fill")
Text("Home")
}
DemoView(screenName: "Result")
.tabItem {
Image.init(systemName: "star.fill")
Text("Result")
}
DemoView(screenName: "More")
.tabItem {
Image.init(systemName: "star.fill")
Text("More")
}
}
}
}
struct DemoView:View {
#State var text:String = ""
var screenName:String
var body: some View {
VStack{
Text(screenName)
.font(.title)
TextField("Buggy Keyboard Issue", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("Issue : When keyboard appears, onAppear of other 2 tabs call automatically.")
.font(.footnote)
}
.padding()
.onAppear(perform: {
debugPrint("OnAppear of : \(screenName)")
})
}
}
This seems to be a bug of SwiftUI 2.0 but not sure.
Any help will be appreciated.
Thanks
Having the same issue myself, I think this is a bug or something like that, however I came up with a solution maybe a workaround until apple will fix it.
The thing that I did is basically I used a LazyVStack, and this seems to be working perfectly.
LazyVStack {
VStack{
Text(screenName)
.font(.title)
TextField("Buggy Keyboard Issue", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("Issue : When keyboard appears, onAppear of other 2 tabs call automatically.")
.font(.footnote)
}
.padding()
.onAppear(perform: {
debugPrint("OnAppear of : \(screenName)")
})
}
Now the OnAppear method of other tab's view it is not called automatically when the keyboard appear.
Just implemented the following workaround:
struct ContentView: View {
var body: some View {
TabView(selection: $selectedTab) {
TabContentView(tag: 0, selectedTag: selectedTab) {
Text("Some tab content")
}
.tabItem {
Text("First tab")
}
TabContentView(tag: 0, selectedTag: selectedTab) {
Text("Another tab content")
}
.tabItem {
Text("Second tab")
}
}
}
#State private var selectedTab: Int = 0
}
private struct TabContentView<Content: View, Tag: Hashable>: View {
init(tag: Tag, selectedTag: Tag, #ViewBuilder content: #escaping () -> Content) {
self.tag = tag
self.selectedTag = selectedTag
self.content = content
}
var body: some View {
Group {
if tag == selectedTag {
content()
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
Color.clear
}
}
.tag(tag)
}
private let tag: Tag
private let selectedTag: Tag
private let content: () -> Content
}
Not sure if it's stable enough but keyboard appearance doesn't trigger onAppear on tabs content anymore.
To avoid reloading your view try with on the TabView
.ignoresSafeArea(.keyboard, edges: .bottom)
It only works on iOS 14

Warning: Attempt to present * on * which is already presenting (null) SwiftUI

I am relatively new to iOS app development as well as SwiftUI and I am currently working on an app that allows the user to create a schedule by the week.
I recently modified the entity to also accept time inputs so that the lists organize themselves based on the time they were scheduled for. Here's a quick look:
Main List View
However, when I click on each item it sends me to the wrong event.
In UIKit I noticed this problem was mainly prevalent due to a call to multiple views being presented, however I could not see where I would be presenting multiple views.
Main List View
List {
ForEach(self.Events) { event in
if("\(event.dotw ?? "Unknown")" == self.name) {
Button(action: {
self.isEvent.toggle()
}) {
HStack {
Text("\(event.name ?? "Unknown")")
.foregroundColor(Color.white)
Spacer()
Text("\(event.time ?? Date(), formatter: Self.taskDateFormat)")
}
}
.sheet(isPresented: self.$isEvent){
EventView(eventName: "\(event.name ?? "Unknown")", description: "\(event.descript ?? "Unknown")", eventTime: (event.time ?? Date())).environment(\.managedObjectContext, self.managedObjectContext)
}
.buttonStyle(ItemButton())
}
}.onDelete { indexSet in
let deleteItem = self.Events[indexSet.first!]
self.managedObjectContext.delete(deleteItem)
do{
try self.managedObjectContext.save()
} catch {
print(error)
}
}
Button(action: {
self.isPresented.toggle()
}) {
HStack {
Text("new")
Spacer()
}
}
.buttonStyle(NewItemButton())
.sheet(isPresented:$isPresented) {
NewEventView(dotw: self.name).environment(\.managedObjectContext, self.managedObjectContext)
}
}
.onAppear {
UITableView.appearance().separatorStyle = .none
}.onDisappear {
UITableView.appearance().separatorStyle = .singleLine
}
.navigationBarHidden(true)
.navigationBarTitle("")
.edgesIgnoringSafeArea(.top)
}
.edgesIgnoringSafeArea(.top)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
This code:
.sheet(isPresented: self.$isEvent){
EventView(eventName: "\(event.name ?? "Unknown")", description: "\(event.descript ?? "Unknown")", eventTime: (event.time ?? Date())).environment(\.managedObjectContext, self.managedObjectContext)
}
Is where I am assuming the problem lies however I could find nothing wrong with it
Event View
struct EventView: View {
var eventName: String
var description: String
var eventTime: Date
var body: some View {
VStack {
VStack {
Image(systemName: "chevron.compact.down")
.foregroundColor(.white)
.imageScale(.large)
.padding()
HStack {
Text(eventName)
.foregroundColor(.white)
.font(.largeTitle)
.padding(.top, 15)
.padding(.leading, 20)
Spacer()
}
.padding(.bottom)
.frame(maxWidth:.infinity)
}
.background(Color.gray)
.shadow(color: Color.black.opacity(0.2), radius: 7, x: 3, y: 3)
.shadow(color: Color.white.opacity(0.7), radius: 4, x: -5, y: -5)
VStack(alignment:.leading) {
HStack {
Text(description)
.padding(.leading, 20)
.padding(.top, 30)
.foregroundColor(.gray)
Spacer()
}
Spacer()
}
.frame(maxWidth:.infinity,maxHeight:.infinity)
}
.edgesIgnoringSafeArea(.top)
}
}
Event Entity Initializer
import Foundation
import CoreData
import SwiftUI
public class Event:NSManagedObject,Identifiable {
#NSManaged public var name: String?
#NSManaged public var createdAt: Date?
#NSManaged public var descript: String?
#NSManaged public var dotw: String?
#NSManaged public var time: Date?
}
extension Event {
static func getAllEvents() -> NSFetchRequest<Event> {
let request: NSFetchRequest<Event> = NSFetchRequest<Event>(entityName: "Event")
let sortDescriptor = NSSortDescriptor(key: "time", ascending:true)
let sortDescriptor2 = NSSortDescriptor(key: "createdAt", ascending: true)
request.sortDescriptors = [sortDescriptor,sortDescriptor2]
return request
}
}
Are your Events identifiable? This helps SwiftUI knowing which is which:
struct Events: Identifiable {
var id = UUID()
var name: String
}
Might also help to put .id(event.id) in your ForEach block to identify it there too.

SwiftUI layout grows outside the bounds of the device when using .edgesIgnoringSafeArea()

Having an issue in SwiftUI where some Views are growing bigger vertically than the size of the device when using .edgesIgnoringSafeArea(.bottom). On an iPhone 11 Pro which is 812 pixels high I am seeing a view of size 846. I am using the Debug View Hierarchy to verify it. This has been tested on Xcode 11.4.1 and 11.1 and exists in both versions and probably all in between.
I have included sample code below.
I am pretty sure this is a SwiftUI bug, but was wondering if anyone has a workaround for it. I need the edgesIgnoringSafeArea(.bottom) code to draw the TabBar, and for the ProfileView() to extend to the bottom of the screen when I hide my custom tab bar.
struct ContentView: View {
var body: some View {
MainTabView()
}
}
struct MainTabView : View {
enum Item : CaseIterable {
case home
case resources
case profile
}
#State private var selected : Item = .home
var body: some View {
VStack(spacing: 0.0) {
ZStack {
HomeView()
.zIndex(selected == .home ? 1 : 0)
ResourcesView()
.zIndex(selected == .resources ? 1 : 0)
ProfileView()
.zIndex(selected == .profile ? 1 : 0)
}
// Code here for building and showing/hiding a Toolbar
// Basically just a HStack with a few buttons in it
}
.edgesIgnoringSafeArea(.bottom) // <- This causes the screen to jump to 846
}
}
struct ProfileView : View {
#State private var showQuestionnaireView = false
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: QuestionnaireView( showQuestionnaireView:$showQuestionnaireView),
isActive: $showQuestionnaireView) {
Text("Show Questionnaire View")
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
}
struct QuestionnaireView : View {
#Binding var showQuestionnaireView : Bool
var body: some View {
GeometryReader { screenGeometry in
ZStack {
Color.orange
VStack {
Text("Top")
Spacer()
Text("Bottom")
}
}
}
}
}
HomeView() and ResourcesView() are just copies of ProfileView() that do their own thing.
When you run it you will see a button, push the button and a hidden Navigation Stack View pushes on the QuestionnaireView, this view contains a VStack with two text fields, neither of which you will be able to see due to this issue. Understandably the top one is behind the notch, but the bottom one is off the bottom of the screen. In my real project this issue is rarely seen at runtime, but switching between dark mode and light mode shows it. In the above code there is no need to switch appearances.
EDIT: FB7677794 for anyone interested, have not received any updates from Apple since lodging it 3 weeks ago.
EDIT2: Added some more code to MainTabBar
Update: This is fixed in Xcode 12 Beta 2
After reading the updated question I have made some changes and tried to make a small demo. In this, I am using the same approach as before, put NavigationView in your main tab view and with this you don't have to hide and show every time you come or leave your main tab view.
import SwiftUI
struct ContentView: View {
var body: some View {
MainTabView()
}
}
struct MainTabView : View {
enum Item : CaseIterable {
case home
case resources
case profile
}
#State private var selected : Item = .home
var body: some View {
NavigationView {
VStack(spacing: 0.0) {
ZStack {
Group {
HomeView()
.zIndex(selected == .home ? 1 : 0)
ResourcesView()
.zIndex(selected == .resources ? 1 : 0)
ProfileView()
.zIndex(selected == .profile ? 1 : 0)
}
.frame(minWidth: .zero, maxWidth: .infinity, minHeight: .zero, maxHeight: .infinity)
.background(Color.white)
}
HStack {
Group {
Image(systemName: "house.fill")
.onTapGesture {
self.selected = .home
}
Spacer()
Image(systemName: "plus.app.fill")
.onTapGesture {
self.selected = .resources
}
Spacer()
Image(systemName: "questionmark.square.fill")
.onTapGesture {
self.selected = .profile
}
}
.padding(.horizontal, 30)
}
.frame(height: 40)
.foregroundColor(Color.white)
.background(Color.gray)
// Code here for building and showing/hiding a Toolbar
// Basically just a HStack with a few buttons in it
}
.edgesIgnoringSafeArea(.bottom)
} // <- This causes the screen to jump to 846
}
}
struct ProfileView : View {
#State private var showQuestionnaireView = false
var body: some View {
// NavigationView {
ZStack {
NavigationLink(destination: QuestionnaireView( showQuestionnaireView:$showQuestionnaireView),
isActive: $showQuestionnaireView) {
Text("Show Questionnaire View")
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
// }
}
}
struct QuestionnaireView : View {
#Binding var showQuestionnaireView : Bool
var body: some View {
GeometryReader { screenGeometry in
ZStack {
Color.orange
VStack {
Text("Top")
Spacer()
Text("Bottom")
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
}
struct HomeView: View {
var body: some View {
NavigationLink(destination: SecondView()) {
Text("Home View")
}
}
}
struct ResourcesView: View {
var body: some View {
NavigationLink(destination: SecondView()) {
Text("Resources View")
}
}
}
struct SecondView: View {
var body: some View {
Text("Second view in navigation")
.background(Color.black)
.foregroundColor(.white)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPhone 11"))
}
}
It is due to undefined size for NavigationView. When you add your custom tab bar component, as in example below, that limits bottom area, the NavigationView will layout correctly.
Tested with Xcode 11.4 / iOS 13.4
struct MainTabView : View {
var body: some View {
VStack(spacing: 0.0) {
ZStack {
Color(.cyan)
ProfileView() // << this injects NavigationView
}
HStack { // custom tab bar
Button(action: {}) { Image(systemName: "1.circle").padding() }
Button(action: {}) { Image(systemName: "2.circle").padding() }
Button(action: {}) { Image(systemName: "3.circle").padding() }
}.padding(.bottom)
}
.edgesIgnoringSafeArea(.bottom) // works !!
}
}

Resources