I would like to use VLCKit with SwiftUI.
why not AVPlayer ? because a some video formats are not supported.
Here is my code:
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
NavigationView
{
VStack{
PlayerView(url: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")
}
}
}
}
struct PlayerView: UIViewRepresentable {
public var url: String
let mediaPlayer = VLCMediaPlayer()
func makeUIView(context: Context) -> UIView {
let controller = UIView()
mediaPlayer.drawable = controller
let uri = URL(string: self.url)
let media = VLCMedia(url: uri!)
mediaPlayer.media = media
mediaPlayer.play()
return controller
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
}
This works and load the video but I would like to add playback controls (Full Screen, Play/Pause, forward, etc.).
I didn't find any documentation about this can anyone help me a bit,
Thanks
I watched this video on youtube
https://www.youtube.com/watch?v=N_u9nsXNvn4
and this
How to show my AVPlayer in a VStack with SwiftUI
Here is my demo
import SwiftUI
struct VlcPlayerDemo: UIViewRepresentable{
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<VlcPlayerDemo>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
class PlayerUIView: UIView, VLCMediaPlayerDelegate {
private let mediaPlayer = VLCMediaPlayer()
override init(frame: CGRect) {
super.init(frame: frame)
let url = URL(string: "rtsp://xxxxx")!//replace your resource here
mediaPlayer.media = VLCMedia(url: url)
mediaPlayer.delegate = self
mediaPlayer.drawable = self
mediaPlayer.play()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
}
struct VlcPlayerDemo_Previews: PreviewProvider {
static var previews: some View {
VlcPlayerDemo()
}
}
Related
In SwiftUI I have a VStack with tap gesture enabled. And this VStack Contains two buttons. One button is from UIKit and other one is from SwiftUI itself.
If I click SwiftUI button, it is working as expected. But when I click UIKit button it is not working instead tap gesture is triggering, reason the tap gesture is enabled for VStack.
How to make UIKit button action works, if the tap gesture is enabled?
I have to support my project from iOS 14 and tap gesture should be enabled for VStack.
SwiftUI Code
var body: some View {
NavigationView {
VStack {
Bridge()
.frame(height: 100)
Button("SwiftUI Button") {
debugPrint("SwiftUI Button Clicked")
}
}
.onTapGesture {
debugPrint("User tapped")
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
Bridge Code
struct Bridge: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
return UIKitButtonView()
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func updateUIView(_ uiView: UIViewType, context: Context) { }
class Coordinator: NSObject {}
}
UIView Code
class UIKitButtonView: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet var uikitButton: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
Bundle.main.loadNibNamed("UIKitButtonView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = self.bounds
}
#IBAction func buttonAction(_ sender: Any) {
debugPrint("UIKit button clicked")
}
}
I am using SwiftUI, so I have a wrapper around the Webview, like this:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
var url: String
let webView = WKWebView()
func makeUIView(context: Context) -> WKWebView {
webView.evaluateJavaScript("navigator.userAgent") { (result, error) in
print(result as! String)
}
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string:url)!)
uiView.load(request)
}
}
How can I execute a function/method whenever the user makes scroll to the website?
To get scroll position updates you need to create coordinator and override makeCoordinator() method and return instance of your coordinator. In makeUIView(_:) method just assign scrollview delegate to context.coordinator (context object in provided in arguments of makeUIView method)
Pass binding from view to coordinator and coordinator is responsible to update that binding. Here is code for that
struct WebView: UIViewRepresentable {
var url: String
#Binding var contentOffset: CGPoint
init(url: String, contentOffset: Binding<CGPoint>) {
self.url = url
_contentOffset = contentOffset
}
let webView = WKWebView()
func makeUIView(context: Context) -> WKWebView {
webView.scrollView.delegate = context.coordinator // assign delegation
webView.evaluateJavaScript("navigator.userAgent") { (result, error) in
print(result as! String)
}
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string:url)!)
uiView.load(request)
}
func makeCoordinator() -> Coordinator {
.init(contentOffset: $contentOffset) // create coordinator for delegation
}
class Coordinator: NSObject, UIScrollViewDelegate {
#Binding var contentOffset: CGPoint
init(contentOffset: Binding<CGPoint>) {
_contentOffset = contentOffset
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
contentOffset = scrollView.contentOffset
}
}
}
WebView with UIScrollViewDelegate support.
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
var url: URL
var scrollViewDelegate: UIScrollViewDelegate?
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.scrollView.delegate = scrollViewDelegate
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.load(request)
}}
Observable ScrollViewDetector
import UIKit.UIScrollView
final class ScrollViewDetector: NSObject, ObservableObject, UIScrollViewDelegate {
#Published var isScrolledEnd = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentLoaded = scrollView.contentSize.height > 1000
let endOfContentReached = scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.size.height < 100
if contentLoaded && endOfContentReached {
isScrolledEnd = true
}
}}
Agreement view with sticky footer button.
import SwiftUI
struct AgreementView: View {
#StateObject var scrollViewDetector = ScrollViewDetector()
var body: some View {
GeometryReader { geometry in
ZStack {
WebView(url: URL(string: "YOUR URL")!, scrollViewDelegate: scrollViewDetector)
// Footer Navigation Link
NavigationLink {
EmptyView()
} label: {
// Your Label
Button("Accept")
.padding()
.background(Color.white.opacity(!scrollViewDetector.isScrolledEnd ? 0.9 : 0.0))
}
.position(x: geometry.width(0.5), y: geometry.height(0.9))
.opacity(!scrollViewDetector.isScrolledEnd ? 0.6 : 1.0)
.disabled(!scrollViewDetector.isScrolledEnd ? true : false)
}
}
}}
I have some code that causes SwiftUI previews to fail but will run successfully in the simulator and on a device. The SwiftUI previews failure diagnostics message is:
PreviewUpdateTimedOutError: Updating took more than 5 seconds
Updating a preview from ViewControllerPreviews in NestedTest.app (9218) took more than 5 seconds.
I have boiled the issue down to a minimal amount of code:
import SwiftUI
import UIKit
let createHostInInit = true
class ViewController: UIViewController {
private var host: TestViewHost? = nil
init() {
if createHostInInit {
host = TestViewHost()
}
super.init(nibName: nil, bundle: nil)
}
// Note: this is here to satisfy the compiler and to allow running this
// in the simulator as part of a default new project.
required init?(coder: NSCoder) {
if createHostInInit {
host = TestViewHost()
}
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
if !createHostInInit {
host = TestViewHost()
}
if let host = self.host {
addChild(host)
self.view.addSubview(host.view)
host.didMove(toParent: self)
host.view.frame = self.view.bounds
}
}
}
class TestViewHost: UIHostingController<TestView> {
init() {
super.init(rootView: TestView())
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
struct TestView: View {
var body: some View {
Text("Hi there!")
}
}
struct UIViewControllerPreviewer: UIViewControllerRepresentable {
let viewController: UIViewController
init(viewController: UIViewController) {
self.viewController = viewController
}
func makeUIViewController(context: Context) -> some UIViewController {
return viewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// Do nothing because this is only for previews
}
}
struct ViewControllerPreviews: PreviewProvider {
static var previews: some View {
return UIViewControllerPreviewer(viewController: ViewController())
}
}
If createHostInInit = true then the previews fail. If createHostInInit = false then the previews work. In either of these cases the UI looks correct in the simulator or on a device. So it would seem that something about the preview environment gets cranky about when a UIHostingController is created before viewDidLoad().
Did I miss some documentation for UIHostingController describing these limitations? Is this a bug?
This can be tested in Xcode by creating a new iOS single view project and dropping this code into ViewController.swift.
Thanks
I am trying to use PKToolPicker from PencilKit (iOS/Swift) from a custom view (which is NOT PKCanvasView). The custom view conforms to PKToolPickerObserver. Everything works fine during compile time but I never get to see the PKToolPicker! If I replace my custom view with PKCanvasView, everything works fine!
I am doing this in SwiftUI with UIViewRepresentable (thus First Responder seems a mystery!).
Here is the SwiftUI view in question:
struct PencilKitView: UIViewRepresentable {
typealias UIViewType = myView
let coordinator = Coordinator()
class Coordinator: NSObject, PKToolPickerObserver {
func toolPickerSelectedToolDidChange(_ toolPicker: PKToolPicker) {
// some code
}
func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) {
// some code
}
}
func makeCoordinator() -> PencilKitView.Coordinator {
return Coordinator()
}
func makeUIView(context: Context) -> myView {
let canvasView = myView()
canvasView.isOpaque = false
canvasView.backgroundColor = UIColor.clear
canvasView.becomeFirstResponder()
if let window = UIApplication.shared.windows.filter({$0.isKeyWindow}).first,
let toolPicker = PKToolPicker.shared(for: window) {
toolPicker.addObserver(canvasView)
toolPicker.addObserver(coordinator)
toolPicker.setVisible(true, forFirstResponder: canvasView)
}
return canvasView
}
func updateUIView(_ uiView: myView, context: Context) {
}
}
If I replace myView with PKCanvasView above, the PKToolPicker will be seen.
For the sake of completeness, here is the MyView stub:
class myView: UIScrollView, PKToolPickerObserver {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
public func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) {
/// some code
}
public func toolPickerSelectedToolDidChange(_ toolPicker: PKToolPicker) {
/// some code
}
public func toolPickerIsRulerActiveDidChange(_ toolPicker: PKToolPicker) {
/// some code
}
public func toolPickerFramesObscuredDidChange(_ toolPicker: PKToolPicker) {
/// some code
}
}
Anyone has succeeded in doing this? Is there some undocumented requirement for adopting PKToolPicker?
Here is simplest demo to show PKToolPicker for any custom UIView in SwiftUI.
Tested with Xcode 11.4 / iOS 13.4
struct ToolPickerDemo: View {
#State private var showPicker = false
var body: some View {
Button("Picker") { self.showPicker.toggle() }
.background(ToolPickerHelper(isActive: $showPicker))
}
}
class PickerHelperView: UIView {
override var canBecomeFirstResponder: Bool { true }
}
struct ToolPickerHelper: UIViewRepresentable {
#Binding var isActive: Bool
func makeUIView(context: Context) -> PickerHelperView {
PickerHelperView()
}
func updateUIView(_ uiView: PickerHelperView, context: Context) {
guard let window = uiView.window else { return }
let picker = PKToolPicker.shared(for: window)
picker?.setVisible(isActive, forFirstResponder: uiView)
DispatchQueue.main.async {
uiView.becomeFirstResponder()
}
}
}
I have a UIViewController working fine in SwiftUI with the below code.
import Foundation
import SwiftUI
final class RTCVideoViewController: UIViewController {
var previewView: RTCEAGLVideoView!
var videoTrack: RTCVideoTrack!
override func viewDidLoad() {
previewView = RTCEAGLVideoView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
previewView.contentMode = UIView.ContentMode.scaleAspectFit
view.addSubview(previewView)
do {
videoTrack = try MediaCapturer.shared.createVideoTrack(videoView: previewView)
} catch {
print(error)
}
}
}
extension RTCVideoViewController : UIViewControllerRepresentable{
public typealias UIViewControllerType = RTCVideoViewController
public func makeUIViewController(context: UIViewControllerRepresentableContext<RTCVideoViewController>) -> RTCVideoViewController {
return RTCVideoViewController()
}
public func updateUIViewController(_ uiViewController: RTCVideoViewController, context: UIViewControllerRepresentableContext<RTCVideoViewController>) {
}
}
In SwiftUI view.
import Combine
import SwiftUI
struct LiveView: View {
#ObservedObject var viewModel: LiveViewModel
init(viewModel: LiveViewModel) {
self.viewModel = viewModel
}
var body: some View {
return ZStack {
RTCVideoViewController()
.edgesIgnoringSafeArea(.top)
}
}
}
So far everything works fine but I need the RTCVideoTrack when created to pass it to the LiveViewModel.
Any ideas what pattern I could implement to get notified when viewDidLoad finished or RTCVideoTrack passed to the LiveViewModel?
I changed a bit the code, it was close but simplified more.
import Foundation
import SwiftUI
struct RTCVideoViewController : UIViewControllerRepresentable {
var viewModel: LiveViewModel
func makeUIViewController(context: Context) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {
print("HostBroadcastViewController:updateUIViewController")
let previewView = RTCEAGLVideoView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
previewView.contentMode = UIView.ContentMode.scaleAspectFit
viewController.view.addSubview(previewView)
do {
let videoTrack = try MediaCapturer.shared.createVideoTrack(videoView: previewView)
self.viewModel.videoTrack = videoTrack
}
catch {
print("ERROR getting RTCVideoTrack")
}
}
}
And in SwiftUI init the RTCVideoViewController with RTCVideoViewController(viewModel: self.viewModel) where ViewModel passed in view from parent.