how to create keyboard app extension in swiftui? - swiftui

I am new to swiftUI.I need to create a keyboard extension in swiftui. I just can't find out how to do that. I am searching on internet for whole day but still can't find out how to do that.
Here is some code that I wrote :
struct ContentView: View {
var body: some View {
VStack{
keyboard()
}
}
}
struct keyboard: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIInputViewController {
let inputVC = UIInputViewController()
return inputVC
}
func updateUIViewController(_ uiViewController: UIInputViewController, context: Context) {
print("some text")
}
}
The above code is written in extension folder's keyboardViewController.swift file and not giving me any kind of keyboard display.
IF I write UIKit UIInputController (the file created itself when we create an extension) code in same file then only I can see a keyboard extension appearing.
I want to design keyboard in UIKit Inputviewcontroller type of class and then display it using UIViewControllerRepresentable in swiftui contentview.
Now my question is-> Is this Approach right?? IF yes then please guide me ahead. IF no then please suggest me the right approach.
Thanks in advance!!

Here's my approach using UIHostingController:
When I added a new target "Keyboard" of keyboard extension, XCode automatically generated a class KeyboardViewController:
class KeyboardViewController: UIInputViewController {
// some code
override func viewDidLoad() {
super.viewDidLoad()
self.nextKeyboardButton = UIButton(type: .system)
// some code
}
// some code
}
I added the view through UIHostingController with my keyboard view as the rootView of it. Then added it and its view as children of KeyboardViewController and the view of KeyboardViewController:
class KeyboardViewController: UIInputViewController {
// some code
override func viewDidLoad() {
super.viewDidLoad()
let hostingController = UIHostingController(rootView: KeyboardView(viewController: self))
view.addSubview(hostingController.view)
addChild(hostingController)
self.nextKeyboardButton = UIButton(type: .system)
// some code
}
// some code
}
And my KeyboardView was like:
struct KeyboardView: View {
var viewController: KeyboardViewController
var body: some View {
// some view as you designed
}
}
It worked for me.

Related

UIDocumentpicker Content are greyed out

I’m using UIViewControllerRepresentable for Document picker presentation in swiftUI. The issue is that I'm not able to select the video audio pdf it's all showing in a frozen manner. I need to fix this issues for the iOS 14 and above version. I'm able to select the file by tap-hold and then release its only works with the simulator in real devices it’s not possible for both the simulator and devices except for the file structure, the rest of the documents are in greyed out.
enter image description here
struct DocumentPicker: UIViewControllerRepresentable {
#ObservedObject var chatViewModel: RedesignChatViewModel
func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPicker>) -> UIDocumentPickerViewController {
let viewController = UIDocumentPickerViewController(forOpeningContentTypes: [.pdf, .mp3, .audio, .video, .movie, .item])
viewController.shouldShowFileExtensions = true
viewController.allowsMultipleSelection = false
viewController.delegate = context.coordinator
return viewController
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {
}
}
extension DocumentPicker {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIDocumentPickerDelegate {
var parent: DocumentPicker
var path: String?
init(_ documentPicker: DocumentPicker) {
self.parent = documentPicker
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
controller.dismiss(animated: true)
}
There are a few mistakes, first you'll need to create a UIViewController and use that to present the UIDocumentPickerViewController. Also you need to change Coordinator(self) to Coordinator() and in makeUIViewController return context.coordinator.myViewController that should be a lazy property. The reason for this is that self you are passing in is immediately out of date because it is a value type. You also need to remove the #ObservedObject and add lets or #Binding vars for your properties. When the repreresentable is init with new values for those properties, updateUIViewController will be called and you can then update the coordinator and view controller with the new values.

How to add a customized InfoWindow to markers in google-maps swift ui?

i tried to make a view like bellow in SwiftUi without any success Customized info window swift ui
Since this question doesn't have too much detail, I will be going off of some assumptions. First, I am assuming that you are calling the MapView through a UIViewControllerRepresentable.
I am not too familiar with the Google Maps SDK, but this is possible through the GMSMapViewDelegate Methods. After implementing the proper GMSMapViewDelegate method, you can use ZStacks to present the image that you would like to show.
For example:
struct MapView: UIViewControllerRepresentable {
var parentView: ContentView
func makeUIViewController(context: Context) {
let mapView = GMSMapView()
return mapView
}
func updateUIViewController(_ uiViewController: GMSMapView, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, GMSMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
//Use the proper Google Maps Delegate method to find out if a marker was tapped and then show the image by doing: parent.parentView.isShowingInformationImage = true.
}
}
In your SwiftUI view that you would like to put this MapView in, you can do the following:
struct ContentView: View {
#State var isShowingInformationImage = false
var body: some View {
ZStack {
if isShowingInformationImage {
//Call the View containing the image
}
MapView(parentView: self)
}
}
}

How to Change NSTextField Font Size When Used in SwiftUI

Below is a demo app with a simple NSTextField in a Mac app. For some reason, the font size won't change no matter what I try.
import Cocoa
import SwiftUI
#main
struct SwiftUIWindowTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State private var text = "Text goes here..."
var body: some View {
FancyTextField(text: $text)
.padding(50)
}
}
struct FancyTextField: NSViewRepresentable {
#Binding var text: String
func makeNSView(context: Context) -> NSTextField {
let textField = NSTextField()
textField.font = NSFont.systemFont(ofSize: 30) //<- Not working
return textField
}
func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.stringValue = text
}
}
That's the whole app. I'm not doing anything else in this simple example. I can change the text color and other attributes, but for some reason the font size doesn't change.
On a whim, I tried changing it on the SwiftUI side as well, but I didn't expect that to work (and it doesn't):
FancyTextField(text: $text)
.font(.system(size: 20))
Any ideas?
This is a particularly weird one:
struct FancyTextField: NSViewRepresentable {
#Binding var text: String
func makeNSView(context: Context) -> NSTextField {
let textField = MyNSTextField()
textField.customSetFont(font: .systemFont(ofSize: 50))
return textField
}
func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.stringValue = text
}
}
class MyNSTextField : NSTextField {
func customSetFont(font: NSFont?) {
super.font = font
}
override var font: NSFont? {
get {
return super.font
}
set {}
}
}
Maybe someone will come up with a cleaner solution to this, but you're right -- the normal methods for just setting .font on the NSTextField do not seem to work here. It seems to be because outside elements (the debugger doesn't give me a good hint) are trying to set the font to system font size 13.
So, my solution is to subclass NSTextField and make the setter for font not responsive. Then, I define a custom setter method, so the only way to get up to the real setter is through my custom method.
A little hacky, but it works.

How to present a full screen AVPlayerViewController in SwiftUI

In SwiftUI, it seems like the best way to set up an AVPlayerViewController is to use the UIViewControllerRepresentable in a fashion somewhat like this...
struct PlayerViewController: UIViewControllerRepresentable {
var videoURL: URL?
private var player: AVPlayer {
return AVPlayer(url: videoURL!)
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.modalPresentationStyle = .fullScreen
controller.player = player
controller.player?.play()
return controller
}
func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
}
}
However from the documentation that the only way to show this controller in a full-screen way is to present it using a sheet.
.sheet(isPresented: $showingDetail) {
PlayerViewController(videoURL: URL(string: "..."))
.edgesIgnoringSafeArea(.all)
}
This doesn't give you a full-screen video with a dismiss button but a sheet modal which can be swiped away instead.
In standard non-SwiftUI Swift, it would seem like the best way would be to present this controller...
let controller = PlayerViewController(videoURL: URL(string: "..."))
self.present(controller, animated: true)
...but SwiftUI doesn't have a self.present as part of it. What would be the best way to present a full-screen video in SwiftUI?
Instead of sheet I would use the solution with ZStack (probably with custom transition if needed), like below
ZStack {
// ... other your content below
if showingDetail { // covers full screen above all
PlayerViewController(videoURL: URL(string: "..."))
.edgesIgnoringSafeArea(.all)
//.transition(AnyTransition.move(edge: .bottom).animation(.default)) // if needed
}
}

Disable swipe-back for a NavigationLink SwiftUI

How can I disable the swipe-back gesture in SwiftUI? The child view should only be dismissed with a back-button.
By hiding the back-button in the navigation bar, the swipe-back gesture is disabled. You can set a custom back-button with .navigationBarItems()
struct ContentView: View {
var body: some View {
NavigationView{
List{
NavigationLink(destination: Text("You can swipe back")){
Text("Child 1")
}
NavigationLink(destination: ChildView()){
Text("Child 2")
}
}
}
}
}
struct ChildView: View{
#Environment(\.presentationMode) var presentationMode
var body:some View{
Text("You cannot swipe back")
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button("Back"){self.presentationMode.wrappedValue.dismiss()})
}
}
I use Introspect library then I just do:
import SwiftUI
import Introspect
struct ContentView: View {
var body: some View {
Text("A view that cannot be swiped back")
.introspectNavigationController { navigationController in
navigationController.interactivePopGestureRecognizer?.isEnabled = false
}
}
}
Only complete removal of the gesture recognizer worked for me.
I wrapped it up into a single modifier (to be added to the detail view).
struct ContentView: View {
var body: some View {
VStack {
...
)
.disableSwipeBack()
}
}
DisableSwipeBack.swift
import Foundation
import SwiftUI
extension View {
func disableSwipeBack() -> some View {
self.background(
DisableSwipeBackView()
)
}
}
struct DisableSwipeBackView: UIViewControllerRepresentable {
typealias UIViewControllerType = DisableSwipeBackViewController
func makeUIViewController(context: Context) -> UIViewControllerType {
UIViewControllerType()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
class DisableSwipeBackViewController: UIViewController {
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if let parent = parent?.parent,
let navigationController = parent.navigationController,
let interactivePopGestureRecognizer = navigationController.interactivePopGestureRecognizer {
navigationController.view.removeGestureRecognizer(interactivePopGestureRecognizer)
}
}
}
You can resolve the navigation controller without third party by using a UIViewControllerRepresentable in the SwiftUI hierarchy, then access the parent of its parent.
Adding this extension worked for me (disables swipe back everywhere, and another way of disabling the gesture recognizer):
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
This answer shows how to configure your navigation controller in SwiftUI (In short, use UIViewControllerRepresentable to gain access to the UINavigationController). And this answer shows how to disable the swipe gesture. Combining them, we can do something like:
Text("Hello")
.background(NavigationConfigurator { nc in
nc.interactivePopGestureRecognizer?.isEnabled = false
})
This way you can continue to use the built in back button functionality.
Setting navigationBarBackButtonHidden to true will lose the beautiful animation when you have set the navigationTitle.
So I tried another answer
navigationController.interactivePopGestureRecognizer?.isEnabled = false
But It's not working for me.
After trying the following code works fine
NavigationLink(destination: CustomView()).introspectNavigationController {navController in
navController.view.gestureRecognizers = []
}
preview
The following more replicates the existing iOS chevron image.
For the accepted answer.
That is replace the "back" with image chevron.
.navigationBarItems(leading: Button("Back"){self.presentationMode.wrappedValue.dismiss()})
With
Button(action: {self.presentationMode.wrappedValue.dismiss()}){Image(systemName: "chevron.left").foregroundColor(Color.blue).font(Font.system(size:23, design: .serif)).padding(.leading,-6)}