Apply ViewModifier to UIViewRepresentable - swiftui

I created a UIViewRespresentable for a UITextField. I'd like to apply modifiers:
myTextField.font(.headline).keyboardType(keyboardType)
This is not working, even if my UIViewRepresentable is simple:
class MyTextField: UITextField { ... }
struct MyFieldField: UIViewRepresentable {
private let field = MyTextField()
func makeUIView(context: Context) -> MyTextField { return field }
func updateUIView(_ uiView: MyTextField, context: Context) {
...
}
}

UITextField is a UIKit component, not a SwiftUI component. It doesn't currently respect .font() or any other SwiftUI modifiers. Furthermore, you cannot currently create a UIFont from a SwiftUI Font. So, although you can do #Environment(\.font) var font: Font to get the current value, you won't be able to do anything useful with it in UIKit land.
You can:
Just set a UIFont directly on your UIViewRepresentable
Use UIFont.TextStyle
Map between Font.TextStyle and UIFont.TextStyle
With any of these you will need to explicitly create a variable on your UIViewRepresentable to hold that value, and then apply it during updateUIView(context:)
struct MyFieldField: UIViewRepresentable {
// Your three options
var myUIFont: UIFont
var myUITextStyle: UIFont.TextStyle
var mySwiftUITextStyle: Font.TextStyle // map this onto UIFont.TextStyle
...
}

Related

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.

SwiftUI on button tap call MKMapView.setRegion

In a NavigationView I have a Button and a MKMapView (called MapView). When the user taps the button the map should zoom to the user's location.
I know how to make use of MKMapView.setRegion to zoom to the users's location, but I can't figure out how the correct way to make MapView aware that it should perform this action when the user taps the button.
If I somehow have a reference to my MapView object, I can call setRegion, but I realize that is imperative and now when I'm learning SwiftUI I try to think declaratively instead.
So I believe I should set a State variable of some type and make MapView listen to changes to that variable. But if I manage to do that, then MapView calling setRegion would be imperative anyway.
So I'm scratching my head here. What should I do?
struct ContentView: View {
#State private var foo: Bool = false
var body: some View {
NavigationView {
MapView()
.navigationBarItems(trailing:
HStack {
Button(action: {
// zoom to user's location
self.foo.toggle()
}) {
Image(systemName: "location")
}
})
}
}
}
struct MapView: UIViewRepresentable {
#Binding var foo: Bool
// if foo is changed, then call zoomToUserLocation()
func zoomToUserLocation() {
// ...
mapView.setRegion(region, animated: true)
}
}
It is not clear where do you get region (or it is set), but on any such binding changed the updateUIView of representable is called, so would do the call there and make it asynchronous, like below
func updateUIView(_ uiView: MKMapView, context: Context) {
// if needed make something conditional here on `foo`
self.zoomToUserLocation()
}
You could use the updateUIView method of UIViewRepresentable. I would it use like this:
struct MapView: UIViewRepresentable {
#Binding var foo: Bool
func updateUIView(_ uiView: MKMapView, context: Context) {
if foo {
self.zoomToUserLocation()
}
}
func zoomToUserLocation() {
// ...
mapView.setRegion(region, animated: true)
}
}
You can also make something conditional like Asperi said.

Where to put delegates of CLLocationManager using Swift UI?

I've been on and off Swift for the years so, sorry if I'm doing something stupid, the Swift UI thing isn't helping my confusion either. I have the code I'm working with at the bottom.
So I'm making a Geofencing app using SwiftUI, I've got the basics up and running but still have a bit to go with it,
I have the Geofencing coordinates and stuff inside the UserData environment variable, which I get off a json elsewhere.
Have a SwiftUI object that has a map out with the current position and initializes a CLLocationManager to deal with geofencing.
I'm trying to implement the delegate on the same file by
locationManager.delegate = self
in the setupManager(),
but it will cause
Cannot assign value of type 'GeofencingView' to type 'CLLocationManagerDelegate?'
The lack of SwiftUI specific information online seems to be causing the most confusion for me at the moment, I think what I should do is,
create a customized NsObject that handles the delegates, but in that case I'm not sure how to pass the #EnviromentObject
Find out how to put the delegates on the UIViewRepresentable object.
Any pointers on what I'm doing wrong would be greatly appreciated.
import SwiftUI
import MapKit
import CoreLocation
struct GeofencingView: UIViewRepresentable {
#EnvironmentObject var userData: UserData
var notification = LocalNotificationManager()
var locationManager = CLLocationManager()
func setupManager() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
self.startGoefenceMonitoring()
}
func startGoefenceMonitoring() {
print("MapListView::startGeofenceMonitoring")
for landmark in self.userData.landmarks {
let moniteringCordinate = CLLocationCoordinate2DMake(landmark.locationCoordinate.longitude, landmark.locationCoordinate.latitude)
let moniteringRegion = CLCircularRegion.init(center: moniteringCordinate, radius: 20.0, identifier: "\(landmark.id)" )
locationManager.startMonitoring(for: moniteringRegion)
}
}
func makeUIView(context: Context) -> MKMapView {
setupManager()
let mapView = MKMapView(frame: UIScreen.main.bounds)
let count = self.userData.landmarks.count
var annotationArray: [MKAnnotation] = []
var num:Int = 0
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
}
struct GeofencingView_Preview: PreviewProvider {
static var previews: some View {
GeofencingView()
.environmentObject(UserData())
}
}

UITextView in a modal sheet is not working

To make UI-based editing of a NSAttributedString property (in a managed object) possible, a UITextView is used instead of a SwiftUI TextField View. The text view is located in a modal view being presented by a sheet function.
.sheet(isPresented: $presentSheet) { ...
(to illustrate and reproduce, the code below is a simplified version of this scenario)
The modal view is used to edit a selected model item that is shown in a list through a ForEach construct. The selected model item is passed as an #Observable object to the modal view.
When selecting an item "A", the modal view and the UITextView correctly shows this model item. If selecting a new item "B", the modal view correctly shows this "B" item. But if "B" is now being edited the change will affect the "A" object.
The reason for this behaviour is probably that the UIViewRepresentable view (representing the UITextView) is only initialised once. Further on from here, this seems to be caused by the way a sheet (modal) view is presented in SwiftUI (state variables are only initialised when the sheet first appear, but not the second time).
I am able to fix this malfunction by passing the selected item as a #Binding instead of an #Observable object, although I am not convinced that this is the right way to handle the situation, especially because everything works nicely, if a SwiftUI TextField is used instead of the UITextView (in the simplified case).
Worth mentioning, I seems to have figured out, what goes wrong in the case with the UITextView - without saying that this solves the problem.
In the code listed below (which repro the problem), the Coordinator's init function has one assignment that initialises the Coordinator with the parent. Since this is value and not a reference assignment, and since the Coordinator only get initialised once, an edit of the UITextView will likely access a wrong parent.
Again, I am not certain about my solution to the problem, is the right one, since everything works fine when using a SwiftUI TextField instead. I therefore hope to see some comments on this problem.
struct ContentView: View {
var states = [StringState("A"), StringState("B"), StringState("C"), StringState("D"), StringState("E")]
#State var presentSheet = false
#State var state = StringState("A")
var body: some View {
VStack {
Text("state = \(state.s)")
ForEach(states) { s in
Button(action: {
self.state = s
self.presentSheet.toggle()
})
{
Text("\(s.s)")
}
}
}
.sheet(isPresented: $presentSheet) {
EditView(state: self.state, presentSheet: self.$presentSheet)
}
}
}
struct EditView: View
{
#ObservedObject var state: StringState
#Binding var presentSheet: Bool
var body: some View {
VStack {
Text("\(state.s)")
TextView(string: $state.s) // Edit Not OK
TextField("", text: $state.s ) // Edit OK
Button(action: {
self.presentSheet.toggle()
})
{ Text("Back") }
}
}
}
struct TextView: UIViewRepresentable
{
#Binding var string: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView
{
let textview = UITextView(frame: CGRect.zero)
textview.delegate = context.coordinator
return textview
}
func updateUIView(_ uiView: UITextView, context: Context)
{
uiView.text = string
}
class Coordinator : NSObject, UITextViewDelegate
{
var parent: TextView
init(_ textView: TextView) {
self.parent = textView
}
func textViewDidChange(_ textView: UITextView)
{
self.parent.string = textView.text!
}
}
}
class StringState: Identifiable, ObservableObject
{
let ID = UUID()
var s: String
init(_ s : String) {
self.s = s
}
}
A couple of changes will fix it:
func updateUIView(_ uiView: UITextView, context: Context)
{
uiView.text = string
context.coordinator.parent = self
}
And also add #Published to your ObservableObject:
class StringState: Identifiable, ObservableObject
{
let ID = UUID()
#Published var s: String
init(_ s : String) {
self.s = s
}
}