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()
}
}
}
Related
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()
}
}
I have a UIKit ViewController that's nested inside a SwiftUI view using ViewControllerRepresentable. The SwiftUI view manages a bit of state (an Int, in this example) that I want to display in the UIKit view. When the user taps a button in the SwiftUI parent view, the state change should be reflected in the UIKit view. I've tried using the #Binding property wrapper to keep the two in sync, but clearly I'm missing something, as my view controller's initialiser throws a compile-time error.
I'm quite new to iOS development so perhaps I'm going in the complete wrong direction here. Any help would be greatly appreciated.
The code is as follows (simplified):
struct ContentView: View {
#State private var currentNumber: Int
init(currentNumber: Int) {
self.currentNumber = currentNumber
}
var body: some View {
FancyLabelViewControllerRepresentable(currentNumber: self.$currentNumber)
Button("Increment") {
self.currentNumber += 1
}
}
}
struct FancyLabelViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = FancyLabelViewController
#Binding var currentNumber: Int
init(currentNumber: Binding<Int>) {
self._currentNumber = currentNumber
}
func makeUIViewController(context: Context) -> FancyLabelViewController {
let fancyLabel = FancyLabelViewController(number: self.currentNumber)
fancyLabel.currentNumberInLabel = self.currentNumber
return fancyLabel
}
func updateUIViewController(_ uiViewController: FancyLabelViewController, context: Context) {
uiViewController.currentNumberInLabel = self.currentNumber
}
}
class FancyLabelViewController: UIViewController {
var label = UILabel()
#Binding var currentNumberInLabel: Int
init(number: Int) {
// Error: 'self' used in property access 'currentNumberInLabel' before 'super.init' call
self.currentNumberInLabel = number
// Error: Property 'self.currentNumberInLabel' not initialized at super.init call
super.init(nibName: nil, bundle: nil)
}
required init(coder: NSCoder) {
fatalError("Not implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
label.text = "\(currentNumberInLabel)"
view = label
}
}
I think you don't need the
#Binding var currentNumberInLabel: Int
because the UIViewControllerRepresentable already takes care of updating the currentNumberInLabel value, but you also needs to update the
label.text = "\(currentNumberInLabel)"
So I did something like
class FancyLabelViewController: UIViewController {
var label = UILabel()
var currentNumberInLabel: Int
init(number: Int) {
self.currentNumberInLabel = number
super.init(nibName: nil, bundle: nil)
}
required init(coder: NSCoder) {
fatalError("Not implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
label.text = "\(currentNumberInLabel)"
view = label
}
func updateLabel() {
label.text = "\(currentNumberInLabel)"
}
}
and call updateLabel from UIViewControllerRepresentable as
func updateUIViewController(_ uiViewController: FancyLabelViewController, context: Context) {
uiViewController.currentNumberInLabel = self.currentNumber
uiViewController.updateLabel()
}
So I am trying to display a splitView with a tabBar but the NavigationView does not stretch all the way to the bottom. Is there something I am missing?
here is the code I am using:
import Foundation
import SwiftUI
protocol SettingsCoordinatorInput: ManagedCoordinator {
}
class SettingsCoordinator: ManagedCoordinator, SettingsCoordinatorInput {
var navigationController = UINavigationController()
weak var viewModel: SettingsViewModel?
lazy var viewController: UIHostingController<SettingsSplitView> = {
let viewModel = SettingsViewModel(coordinator: self)
self.viewModel = viewModel
return UIHostingController(rootView: SettingsSplitView())
}()
override func topController() -> UIViewController? {
return viewController
}
convenience init(delegate: ManagedCoordinator) {
self.init()
self.delegate = delegate
}
override func start() {
navigationController.viewControllers = [viewController]
}
#objc private func close() {
finish()
}
override func finish() {
super.finish()
viewController.navigationController?.dismiss(animated: true, completion: nil)
}
}
and the result is like this:
I am making a SwiftUI Button from a custom UIButton class called UIPillButton. Here is the code for creating the SwiftUI button:
Button(action: {
print("Button tapped")
}) {
PillButton()
.padding(.top)
}
Here is my class for creating a SwiftUI view called PillButton which converts my custom UIButton over:
struct PillButton: UIViewRepresentable {
var ntPillButton = NTPillButton(type: .filled, title: "Start Test")
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject {
var parent: PillButton
init(_ pillButton: PillButton) {
self.parent = pillButton
super.init()
}
}
func makeUIView(context: UIViewRepresentableContext<PillButton>) -> UIView {
let view = UIView()
view.addSubview(ntPillButton)
NSLayoutConstraint.activate([
ntPillButton.leadingAnchor.constraint(equalTo: view.leadingAnchor),
ntPillButton.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PillButton>) {}
}
The issue is that the Button is not running the action if I tap on the PillButton itself. It only works if I select the buffer (padding) space above the PillButton. How can I make it so I can use the custom PillButton class as a normal SwiftUI button?
It is not clear what is NTPillButton, but if it is subclass of UIButton then the following demo of generic approach (using base UIButton) should clear and applicable.
Tested with Xcode 11.4 / iOS 13.4
The below gives this simple usage
PillButton(title: "Start Test") {
print("Button tapped")
}
.frame(maxWidth: .infinity) // << screen-wide
.padding(.top)
so now PillButton demo itself:
struct PillButton: UIViewRepresentable {
let title: String
let action: () -> ()
var ntPillButton = UIButton()//NTPillButton(type: .filled, title: "Start Test")
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject {
var parent: PillButton
init(_ pillButton: PillButton) {
self.parent = pillButton
super.init()
}
#objc func doAction(_ sender: Any) {
self.parent.action()
}
}
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setTitle(self.title, for: .normal)
button.addTarget(context.coordinator, action: #selector(Coordinator.doAction(_ :)), for: .touchDown)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {}
}
I'm trying to observe/get some of SwiftUI's List scrolling attributes by wrapping/injection of UIScrollView using UIViewRepresentable.
I'm getting inconsistent behavior with bindings.
Pressing the buttons and changing values depending on if the button is in the parent vs child view has different results.
The bindings from my ObservableObject ScrollInfo class and the UIViewRepresentable start fine, but then break, unless the whole screen is refreshed and makeUIView runs again (like changing to a different tab).
Is there a way to force the UIViewRepresentable to run makeUIView again on a binding update? Or something that will fix this?
I'd like for isScrolling values to be updated and working all the time.
I set up a test to change the colors of the circles to red if the user is dragging the scrollview down. It works initially but stops if I update a value from the ObservableObject in the parent view.
Screenshots of Test from code below
Bindings keep working with bottom button press (updating ObservableObject) in child view
Bindings break with top button press (updating ObservableObject) in parent view
// Parent View
import SwiftUI
struct ContentView: View {
#ObservedObject var scrollInfo:ScrollInfo = ScrollInfo()
var body: some View {
VStack
{
Button(action:{
self.scrollInfo.contentLoaded = true;
})
{
Text("REFRESH")
}
TestView()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Child View
import SwiftUI
struct TestView: View {
#State var test1:String = "Test1"
#ObservedObject var scrollInfo:ScrollInfo = ScrollInfo()
var body: some View {
VStack
{
Button(action:{
self.scrollInfo.contentLoaded.toggle()
}) {
Text("REFRESH")
}
List{
VStack {
VStack{
Text(String( self.scrollInfo.contentLoaded))
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
} .padding(.bottom,620).padding(.top,20)
.background(
ListScrollingHelper(scrollInfo: self.scrollInfo)// injection
)
}.padding(.top,4).onAppear(perform:{
})
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
// ScrollInfo Class
class ScrollInfo: ObservableObject {
#Published var isScrolling:Bool = false
#Published var text:String = "Blank"
#Published var contentLoaded:Bool = false
init()
{
print( "scrollInfo init")
}
deinit {
print("scrollInfo denit")
}
}
// UIViewRepresentable
final class ListScrollingHelper: UIViewRepresentable {
var scrollInfo:ScrollInfo
#Published var scrollView: UIScrollView?
init( scrollInfo:ScrollInfo) {
print("init UIViewRepresentable listscrolling helper")
self.scrollInfo = scrollInfo
}
func makeUIView(context: Context) -> UIView {
//self.uiScrollView.delegate = context.coordinator
print("makeview")
return UIView()
//return self.uiScrollView // managed by SwiftUI, no overloads
}
func catchScrollView(for view: UIView) {
print("checking for scrollview")
if nil == scrollView {
scrollView = view.enclosingScrollView()
if(scrollView != nil)
{
print("scrollview found")
}
}
}
func updateUIView(_ uiView: UIView, context: Context) {
catchScrollView(for: uiView)
if(scrollView != nil)
{
scrollView!.delegate = context.coordinator
}
print("updatingUIView")
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject,UIScrollViewDelegate {
var parent: ListScrollingHelper
init(_ listScrollingHelper: ListScrollingHelper) {
self.parent = listScrollingHelper
print("init coordinator")
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// DispatchQueue.main.async {
if(!self.parent.scrollInfo.isScrolling)
{
self.parent.scrollInfo.isScrolling = true
//self.parent.scrollInfo.text = "scroll"
// }
}
print("start scroll")
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if(self.parent.scrollInfo.isScrolling && !decelerate)
{
self.parent.scrollInfo.isScrolling = false
}
print("end scroll")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if(self.parent.scrollInfo.isScrolling)
{
self.parent.scrollInfo.isScrolling = false
}
print("end scroll")
}
deinit
{
print("deinit coordinator")
}
}
deinit {
print("deinit UIViewRepresentable listscrolling helper")
}
}
extension UIView {
func enclosingScrollView() -> UIScrollView? {
var next: UIView? = self
repeat {
next = next?.superview
if let scrollview = next as? UIScrollView {
return scrollview
}
} while next != nil
return nil
}
}
EDIT - WORKAROUND
I wasn't able to get it working with the ObservableObject or EnvironmentObject, but I was able to get it working with #State and #Binding, although it's a limited amount of info passed back. (ScrollInfo is still there only to use to testing changing a parent ObservableObject)
Hope this helps someone else!
import SwiftUI
struct TestView: View {
#State var isScrolling:Bool = false;
var body: some View {
VStack
{
Button(action:{
self.scrollInfo.contentLoaded.toggle()
}) {
Text("REFRESH")
}
List{
VStack {
VStack{
Text("isScrolling")
Text(String(self.isScrolling))
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
} .padding(.bottom,620).padding(.top,20).background( ListScrollingHelper(isScrolling: self.$isScrolling))
}.padding(.top,4)
}
}
}
extension UIView {
func enclosingScrollView() -> UIScrollView? {
var next: UIView? = self
repeat {
next = next?.superview
if let scrollview = next as? UIScrollView {
return scrollview
}
} while next != nil
return nil
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
final class ListScrollingHelper: UIViewRepresentable {
#Binding var isScrolling:Bool
private var scrollView: UIScrollView?
init(isScrolling:Binding<Bool>)
{
self._isScrolling = isScrolling
}
func makeUIView(context: Context) -> UIView {
return UIView()
}
func catchScrollView(for view: UIView) {
if nil == scrollView {
scrollView = view.enclosingScrollView()
}
}
func updateUIView(_ uiView: UIView, context: Context) {
catchScrollView(for: uiView)
if(scrollView != nil)
{
scrollView!.delegate = context.coordinator
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject,UIScrollViewDelegate {
var parent: ListScrollingHelper
init(_ listScrollingHelper: ListScrollingHelper) {
self.parent = listScrollingHelper
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if(!self.parent.isScrolling)
{
self.parent.isScrolling = true
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if(self.parent.isScrolling && !decelerate)
{
self.parent.isScrolling = false
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if(self.parent.isScrolling)
{
self.parent.isScrolling = false
}
}
deinit
{
}
}
deinit {
}
}
class ScrollInfo: ObservableObject {
#Published var isScrolling:Bool = false
#Published var text:String = "Blank"
#Published var contentLoaded:Bool = false
}