I am trying to make custom keyboard extension. Keyboard buttons showing nicely but no action triggered! Here is Buttons UI:
struct MyKeyButtons: View {
let data: [String] = ["A", "B", "C"]
var body: some View {
HStack {
ForEach(data, id: \.self) { aData in
Button(action: {
KeyboardViewController().keyPressed()
}) {
Text(aData)
.fontWeight(.bold)
.font(.title)
.foregroundColor(.purple)
.padding()
.border(Color.purple, width: 5)
}
}
}
}
}
The KeyboardViewController code here:
import SwiftUI
class KeyboardViewController: UIInputViewController {
#IBOutlet var nextKeyboardButton: UIButton!
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
let child = UIHostingController(rootView: MyKeyButtons())
//that's wrong, it must be true to make flexible constraints work
// child.translatesAutoresizingMaskIntoConstraints = false
child.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(child.view)
addChild(child)//not sure what is this for, it works without it.
// Perform custom UI setup here
self.nextKeyboardButton = UIButton(type: .system)
self.nextKeyboardButton.setTitle(NSLocalizedString("Next Keyboard", comment: "Title for 'Next Keyboard' button"), for: [])
self.nextKeyboardButton.sizeToFit()
self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = false
self.nextKeyboardButton.addTarget(self, action: #selector(handleInputModeList(from:with:)), for: .allTouchEvents)
self.view.addSubview(self.nextKeyboardButton)
self.nextKeyboardButton.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.nextKeyboardButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
override func viewWillLayoutSubviews() {
self.nextKeyboardButton.isHidden = !self.needsInputModeSwitchKey
super.viewWillLayoutSubviews()
}
override func textWillChange(_ textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(_ textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
textColor = UIColor.white
} else {
textColor = UIColor.black
}
self.nextKeyboardButton.setTitleColor(textColor, for: [])
}
//==================================
func keyPressed() {
print("test--- clicked! ")
//textDocumentProxy.insertText("a")
(textDocumentProxy as UIKeyInput).insertText("a")
}
}
For more info see the GitHub project: https://github.com/ask2asim/KeyboardTest1
Related
Is it possible to add a view modifier inside .onChange?
Simplified example:
content
.onChange(of: publishedValue) {
content.foregroundColor(.red)
}
I have a theme that when changed needs to change the status bar color. I have a view modifier created for that ( https://barstool.engineering/set-the-ios-status-bar-style-in-swiftui-using-a-custom-view-modifier/ ). The modifier works fine, but I need to update it as the publishedValue changes.
Actual minimal example:
import SwiftUI
struct ContentView: View {
#ObservedObject var viewModel: TestViewModel
var body: some View {
ZStack {
Rectangle().foregroundColor(.mint)
VStack(alignment: .center, spacing: 25) {
Text("Test text \(viewModel.publishedValue)")
.onChange(of: viewModel.publishedValue) { newValue in
// Change status bar color
if viewModel.publishedValue % 2 == 0 {
self.body.statusBarStyle(.lightContent)
} else {
self.body.statusBarStyle(.darkContent)
}
}
Button("Increment") {
viewModel.publishedValue += 1
}
}
}
.ignoresSafeArea()
.statusBarStyle(.lightContent)
}
}
class TestViewModel: ObservableObject {
#Published var publishedValue: Int
init(publishedValue: Int) {
self.publishedValue = publishedValue
}
}
extension View {
/// Overrides the default status bar style with the given `UIStatusBarStyle`.
///
/// - Parameters:
/// - style: The `UIStatusBarStyle` to be used.
func statusBarStyle(_ style: UIStatusBarStyle) -> some View {
return self.background(HostingWindowFinder(callback: { window in
guard let rootViewController = window?.rootViewController else { return }
let hostingController = HostingViewController(rootViewController: rootViewController, style: style)
window?.rootViewController = hostingController
}))
}
}
fileprivate class HostingViewController: UIViewController {
private var rootViewController: UIViewController?
private var style: UIStatusBarStyle = .default
init(rootViewController: UIViewController, style: UIStatusBarStyle) {
self.rootViewController = rootViewController
self.style = style
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let child = rootViewController else { return }
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return style
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setNeedsStatusBarAppearanceUpdate()
}
}
fileprivate struct HostingWindowFinder: UIViewRepresentable {
var callback: (UIWindow?) -> ()
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async { [weak view] in
self.callback(view?.window)
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
// ...
}
}
GitHub repo for the example project: https://github.com/Iikeli/view-modifier-test
You are way overcomplicating this. Since your viewModel is #ObservedObject and the publishedValue is Published, the body of your View will be recalculated automatically every time publishedValue is updated. There's no need for a manual onChange.
You can simply move the logic into the input argument of statusBarStyle.
var body: some View {
ZStack {
Rectangle().foregroundColor(.mint)
VStack(alignment: .center, spacing: 25) {
Text("Test text \(viewModel.publishedValue)")
Button("Increment") {
viewModel.publishedValue += 1
}
}
}
.ignoresSafeArea()
.statusBarStyle(viewModel.publishedValue % 2 == 0 ? .lightContent : .darkContent)
}
Or even better, move the logic into a separate computed property:
var body: some View {
....
.statusBarStyle(statusBarStyle)
}
private var statusBarStyle: UIStatusBarStyle {
viewModel.publishedValue % 2 == 0 ? .lightContent : .darkContent
}
The short answer is no, but that doesn't mean you can't use it to have views change based on some .onChange(..) action. For example.
#State var somethingChanged = false
Text(somethingChanged ? "First Value" : "Second Value")
// Your code/view
.onChange(..) {
//Some Condition or whatever you want.
somethingChanged = true
}
Your usage might look something like this.
content
.foregroundColor(somethingChanged ? .red : .blue)
.onChange(ofPublishedValue) {
somethingChanged = true
}
First of all, thanks for the help. Neither of the answers helped in my situation, since I couldn't get the modifier to update with the variable change. But with some Googling and trying out different solutions I figured out a working solution for updating the status bar colors.
I needed to update the style variable in the HostingViewController, and then update accordingly. So I added the HostingViewController as a #StateObject and updated the style variable inside the .onChange(). Not quite the solution I was going with to start out, but it does work.
The code:
import SwiftUI
import Introspect
struct ContentView: View {
#ObservedObject var viewModel: TestViewModel
#StateObject var hostingViewController: HostingViewController = .init(rootViewController: nil, style: .default)
var body: some View {
ZStack {
Rectangle().foregroundColor(.mint)
VStack(alignment: .center, spacing: 25) {
Text("Test text \(viewModel.publishedValue)")
.onChange(of: viewModel.publishedValue) { newValue in
// Change status bar color
if viewModel.publishedValue % 2 == 0 {
hostingViewController.style = .lightContent
} else {
hostingViewController.style = .darkContent
}
}
Button("Increment") {
viewModel.publishedValue += 1
}
}
}
.ignoresSafeArea()
.introspectViewController { viewController in
let window = viewController.view.window
guard let rootViewController = window?.rootViewController else { return }
hostingViewController.rootViewController = rootViewController
window?.rootViewController = hostingViewController
}
}
}
class TestViewModel: ObservableObject {
#Published var publishedValue: Int
init(publishedValue: Int) {
self.publishedValue = publishedValue
}
}
class HostingViewController: UIViewController, ObservableObject {
var rootViewController: UIViewController?
var style: UIStatusBarStyle = .default {
didSet {
self.rootViewController?.setNeedsStatusBarAppearanceUpdate()
}
}
init(rootViewController: UIViewController?, style: UIStatusBarStyle) {
self.rootViewController = rootViewController
self.style = style
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let child = rootViewController else { return }
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return style
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setNeedsStatusBarAppearanceUpdate()
}
}
Big shoutout to this answer for giving me all that I needed to implement the solution. Note: You can override the rootViewController however you like, I used SwiftUI-Introspect as we are already using it in our project anyway.
GitHub branch
UIAction.captureTextFromCamera(responder: context.coordinator, identifier: nil)
This method must be placed in a Menu before it can be used. What if I want to call this method when I press the Button?
And I wanna use it in the SwiftUI, like this
#State var text = ""
Text(text)
Menu {
Button(action: {
// TODO: support live text
}) {
Label("Live Text", systemImage: "camera.viewfinder")
}
} label: {
Text("Hi")
}
import SwiftUI
#available(iOS 15.0, *)
struct ScanTextView: View {
#State var text: String = ""
#State var message: String = ""
var body: some View {
VStack {
Text(message)
HStack{
TextField("Enter text",text: $text)
ScanTextView_UI(text: $text, type: .buttonMenu,imageSystemName: "ellipsis.circle", additionalActions: [
//https://developer.apple.com/documentation/uikit/uiaction
UIAction(title: "Refresh") { (action) in
//Just sample
self.message = "refresh"
},
UIAction(title: "Anything", image: UIImage(systemName: "checkmark")) { (action) in
//Just sample
self.message = "anything"
},
UIAction(title: "Clear message") { (action) in
//Just sample
self.message = ""
}
])
}.border(Color.red)
TextField("Enter text",text: $text)
.border(Color.yellow)
.toolbar(content: {
ToolbarItem(placement: .keyboard, content: {
ScanTextView_UI(text: $text, type: .buttonMenu)
})
})
ScanTextView_UI(text: $text, type: .textField, additionalActions: [
UIAction(title: "Anything", image: UIImage(systemName: "checkmark")) { (action) in
//Just sample
self.message = "anything"
}
]).border(Color.orange)
HStack{
TextField("Enter text",text: $text)
ScanTextView_UI(text: $text, type: .button)
}.border(Color.green)
}
}
}
#available(iOS 15.0, *)
struct ScanTextView_UI: UIViewRepresentable {
#Binding var text: String
let type: Types
var imageSystemName: String = "text.viewfinder"
var additionalActions: [UIAction] = []
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> some UIView {
let textFromCamera = UIAction.captureTextFromCamera(responder: context.coordinator, identifier: nil)
let image = UIImage(systemName: imageSystemName)
//button
let button = UIButton(frame: .zero, primaryAction: textFromCamera)
button.setImage(image, for: .normal)
//buttonMenu
// A UIButton can hold the menu, it is a long press to get it to come up
let buttonMenu = UIButton()
var temp: [UIAction] = [textFromCamera]
for elem in additionalActions{
temp.append(elem)
}
let cameraMenu = UIMenu(children: temp)
buttonMenu.setImage(image, for: .normal)
buttonMenu.menu = cameraMenu
//TextField
// Or a textField
let toolbarItem = UIBarButtonItem(title: nil, image: image, primaryAction: textFromCamera, menu: nil)
var temp2: [UIBarButtonItem] = [toolbarItem]
for elem in additionalActions{
temp2.append(UIBarButtonItem(title: elem.title, image: elem.image, primaryAction: elem, menu: nil))
}
let textField = UITextField()
let bar = UIToolbar()
bar.items = temp2
bar.sizeToFit()
textField.inputAccessoryView = bar
textField.placeholder = "Enter text"
textField.delegate = context.coordinator
textField.text = text
var result: UIView = UIView()
switch type {
case .textField:
result = textField
case .buttonMenu:
result = buttonMenu
case .button:
if !additionalActions.isEmpty{
result = buttonMenu
}else{
result = button
}
}
return result
}
func updateUIView(_ uiView: UIViewType, context: Context) {
if uiView is UITextField{
(uiView as! UITextField).text = text
}
}
//Making the Coordinator a UIResponder as! UIKeyInput gives access to the text
class Coordinator: UIResponder, UIKeyInput, UITextFieldDelegate{
var hasText: Bool{
!parent.text.isEmpty
}
let parent: ScanTextView_UI
init(_ parent: ScanTextView_UI){
self.parent = parent
}
func insertText(_ text: String) {
//Update the #Binding
parent.text = text
}
func deleteBackward() { }
// MARK: UITextFieldDelegate
func textFieldDidBeginEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
enum Types{
case textField
case buttonMenu
case button
}
}
#available(iOS 15.0, *)
struct ActionMenuView_Previews: PreviewProvider {
static var previews: some View {
ScanTextView()
}
}
I'm trying to implement pop up alert, which basicly are view that appears on another view, how can i dismiss this alert view on top, when i tap outside of white box? screen
My logic of calling this alert: I have my main view, where i have facebook signIn button, which should call this alert view, my alert view is just another swiftui view, which has UIViewRepresentable object inside to make hyperlinks inside my text.
MAIN VIEW:
//
// WelcomeView.swift
//
//
//
//
import SwiftUI
import FacebookLogin
import SDWebImageSwiftUI
import NavigationStack
import AVKit
struct WelcomeView: View {
#State var showTermsAndConditionsAlert: Bool = false
ZStack { // 1 outer
ZStack { // 2 inner
VStack() {
ZStack(alignment: .top) {
// my video player here
HStack {
// some other ui elements
}
VStack {
// logo
}
}
}
HStack(alignment: .bottom) {
VStack {
Button(action: {
appLog.debug("Continue with Facebook")
showTermsAndConditionsAlert = true
}) {
AuthButtonView(imageIdentifier: "ic_facebook", buttonTitle: "welcome_page_sign_up_btn_fb".localized)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(5)
}
// email button sign up
}
// email button sign in
}) {
}
// push views
}
.ignoresSafeArea()
}
}
.padding(.bottom, Theme.pageBottomPadding)
if showTermsAndConditionsAlert {
TermsConditionsAlertView() {
self.showTermsAndConditionsAlert = false
self.viewModel.facebookLogin()
}
}
}
.onTapGesture(perform: {
// TODO: close dialog only when it is opened and press outside of dialog bound
self.showTermsAndConditionsAlert = false
})
}
.navigationBarTitle("", displayMode: .automatic)
.onAppear {
self.player.isMuted = true
self.player.play()
}
.onDisappear {
self.player.pause()
}
.onReceive(viewModel.$state.dropFirst(), perform: { state in
// switch state
}
})
}
}
ALERT VIEW: HyperLinkTextView - uirepresantable uiview
//
// TermsConditionsAlertView.swift
//
//
//
//
import SwiftUI
import NavigationStack
struct TermsConditionsAlertView: View {
var callback: (() -> ())?
var body: some View {
ZStack {
Color.black.opacity(0.8).edgesIgnoringSafeArea(.all)
VStack(alignment: .leading, spacing: 0) {
// Text here
HyperLinkTextView(text: "terms_conditions_alert_view_description".localized,
links: [Hyperlink(word: "terms_conditions".localized, url: NSURL(string: Constants.termsURL)!),
Hyperlink(word: "privacy_policy".localized, url: NSURL(string: Constants.privacyPolicyURL)!)])
Button(action: {
appLog.debug("Agree and sign up pressed")
callback?()
}) {
// button struct
}
}
}
}
}
HYPERLINK UI VIEW:
//
// HyperLinkTextView.swift
//
//
//
//
import SwiftUI
struct Hyperlink {
var word: String
var url: NSURL
}
struct HyperLinkTextView: UIViewRepresentable {
private var text: String
private var links: [Hyperlink]
init(text: String, links: [Hyperlink]) {
self.text = text
self.links = links
}
func makeUIView(context: Self.Context) -> UITextView {
let attributedString = NSMutableAttributedString(string: text)
links.forEach { hyperlink in
let linkAttributes = [NSAttributedString.Key.link: hyperlink.url]
var nsRange = NSMakeRange(0, 0)
if let range = text.range(of: hyperlink.word) {
nsRange = NSRange(range, in: text)
}
attributedString.setAttributes(linkAttributes, range: nsRange)
attributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSNumber(value: 1), range: nsRange)
}
let textView = UITextView()
textView.isEditable = false
textView.delegate = context.coordinator
textView.attributedText = attributedString
textView.linkTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.blue]
textView.isUserInteractionEnabled = true
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.font = UIFont(name: "ArialMT", size: 18)
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
public class Coordinator: NSObject, UITextViewDelegate, NSLayoutManagerDelegate {
weak var textView: UITextView?
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction, replacementText text: String) -> Bool {
return true
}
}
}
Make a full screen view with a ZStack containing a view that is translucent, and put your alert on top of that. Add a tap gesture to the translucent view that dismisses the entire view.
I am new to swift and I am having treouble making my SwiftUI and UIkit pices of code talk to each other.
I have a variable in my swiftUI that I want to transfer to the UIkit piece or vice versa.
This is my swfitUI code and I want to pass someString to the UIKit code
struct ForceTestView: View {
//MARK: - PROPERTIES
//MARK: - BODY
var body: some View {
VStack(spacing: 20){
TouchesSwiftUI()
.border(Color.gray, width: 5)
.frame(maxWidth: 200, maxHeight: 200)
Text("\(someString)")
.onTapGesture {
someString = "New Value"
}
}//:VSTACK
}//: BODY
struct TouchesSwiftUI: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> TouchesHelper {
let touchControl = TouchesHelper()
return touchControl
}
func updateUIViewController(_ uiViewController: TouchesHelper, context: Context) {
}
}
This is the UIkit code that I have
import Foundation
import UIKit
import SwiftUI
import Combine
class TouchesHelper: UIViewController{
var forceLabel: UILabel!
var forceText: String = "frcTxt Empty"
var xPosLabel: UILabel!
override func loadView() {
view = UIView()
view.backgroundColor = .white
forceLabel = UILabel()
forceLabel.translatesAutoresizingMaskIntoConstraints = false
forceLabel.textAlignment = .left
forceLabel.font = UIFont.systemFont(ofSize: 18)
forceLabel.text = "Force Readings"
forceLabel.numberOfLines = 0
view.addSubview(forceLabel)
xPosLabel = UILabel()
xPosLabel.translatesAutoresizingMaskIntoConstraints = false
xPosLabel.textAlignment = .left
xPosLabel.font = UIFont.systemFont(ofSize: 18)
xPosLabel.text = "Finger X Position"
xPosLabel.numberOfLines = 0
view.addSubview(xPosLabel)
NSLayoutConstraint.activate([
forceLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
forceLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
xPosLabel.topAnchor.constraint(equalTo: forceLabel.bottomAnchor),
xPosLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let userForce = touch.force
forceLabel.text = "Force: \(userForce)"
forceText = "\(userForce)"
print("UI \(testString)")
}//:if touch
}//: touchesMoved
}
I would want to pass the userForce to swiftUI, and then update something like the userxPosLabel with the text input from SwiftUI
I have tried to look at #Obverbable objects and coordinators, but they don't really make sense or work because they don't help in passing things that are connected to touching events within TOuchesMoved (I am coding this for a project that uses an iPhoneX, which had 3D touch)
Any help will be really appreciated!
Thank you!
There are a couple of different ways you can approach the specifics of this, but your inclination to use an ObservableObject like you mentioned is a good one.
Code first, and then some explanation:
class ViewModel : ObservableObject {
#Published var force : String = ""
#Published var xPos : String = ""
}
struct ContentView : View {
#StateObject var viewModel = ViewModel()
//MARK: - BODY
var body: some View {
VStack(spacing: 20){
TouchesSwiftUI(viewModel: viewModel, force: viewModel.force, xPos: viewModel.xPos)
.border(Color.gray, width: 5)
.frame(maxWidth: 200, maxHeight: 200)
Text(viewModel.force)
.onTapGesture {
viewModel.force = "New Value"
}
Text(viewModel.xPos)
}//:VSTACK
}//: BODY
}
struct TouchesSwiftUI: UIViewControllerRepresentable {
var viewModel : ViewModel
var force: String
var xPos: String
func makeUIViewController(context: Context) -> TouchesHelper {
let touchControl = TouchesHelper()
return touchControl
}
func updateUIViewController(_ uiViewController: TouchesHelper, context: Context) {
uiViewController.viewModel = viewModel
uiViewController.forceLabel.text = force
uiViewController.xPosLabel.text = xPos
}
}
class TouchesHelper: UIViewController{
var viewModel : ViewModel?
// {
// didSet {
// guard let viewModel = viewModel else { return }
// cancellable = viewModel.objectWillChange.sink(receiveValue: { _ in
// DispatchQueue.main.async {
// forceLabel.text = viewModel.force
// xPosLabel.text = viewModel.
// }
// })
// }
// }
var forceLabel: UILabel!
var xPosLabel: UILabel!
private var cancellable : AnyCancellable?
override func loadView() {
view = UIView()
view.backgroundColor = .white
forceLabel = UILabel()
forceLabel.translatesAutoresizingMaskIntoConstraints = false
forceLabel.textAlignment = .left
forceLabel.font = UIFont.systemFont(ofSize: 18)
forceLabel.text = "Force Readings"
forceLabel.numberOfLines = 0
view.addSubview(forceLabel)
xPosLabel = UILabel()
xPosLabel.translatesAutoresizingMaskIntoConstraints = false
xPosLabel.textAlignment = .left
xPosLabel.font = UIFont.systemFont(ofSize: 18)
xPosLabel.text = "Finger X Position"
xPosLabel.numberOfLines = 0
view.addSubview(xPosLabel)
NSLayoutConstraint.activate([
forceLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
forceLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
xPosLabel.topAnchor.constraint(equalTo: forceLabel.bottomAnchor),
xPosLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let userForce = touch.force
//forceLabel.text = "Force: \(userForce)"
//xPosLabel.text = "X: \(touch.location(in: self.view).x)"
viewModel?.force = "\(userForce)"
viewModel?.xPos = "X: \(Int(touch.location(in: self.view).x))"
}//:if touch
}//: touchesMoved
}
What's happening:
ViewModel holds a shared state. This controls what's on the SwiftUI Text labels on ContentView and gets passed to the TouchesSwiftUI which in turn passes it to TouchesHelper
In touchesMoved, TouchesHelper updates ViewModel -- the changes in the labels end up getting propagated back through to TouchesHelper updateUIViewController
You can see that your onTapGesture affects the state as you would expect in both ContentView and TouchesHelper
I left a bit commented in TouchesHelper -- this is just to show that if you wanted to subscribe to updates via publishers on the ViewModel you could do that to change the label text rather than passing then back through updateUIViewController
I am woking on an iOS Custom Keyboard Extension. SwiftUI buttons are showing properly but never gets called!
import SwiftUI
class KeyboardViewController: UIInputViewController {
override func viewDidLoad() {
super.viewDidLoad()
let vc = UIHostingController(rootView: MyKeyButtons())
vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(vc.view)
}
}
struct MyKeyButtons: View {
let data: [String] = ["A", "B", "C"]
var body: some View {
HStack {
ForEach(data, id: \.self) { aData in
Button(action: {
print("button pressed!") // Not working!
}) {
Text(aData).fontWeight(.bold).font(.title)
.foregroundColor(.white).padding()
.background(Color.purple)
}
}
}
}
}
For easier understanding, here is the full: https://github.com/ask2asim/KeyboardTest1