SwiftUI/UIKit How to convert SwiftUI Font to UIKit UIFont? - swiftui

Due to some limitations in SwiftUI Text. I've to use the UIText instead.
In an UIViewRepresentable,
func makeUIView(context: Context) -> UITextView {
let vw = UITextView()
let env = context.environment
// UIFont? Font?
vw.font = env.font
...
}
I want to initialize the UIKit UILabel using the SwiftUI EnvironmentValues.
More specifically, assign an SwiftUI Font to UIKit UIFont.
The question is, how to convert a Font to UIFont?

If you need share same font, then use UIFont as model property and convert in SwiftUI where needed as, for example
Text("Some text")
.font(Font(uiFont as CTFont))

Create extension:
extension Font {
init(_ uiFont: UIFont) {
self = Font(uiFont as CTFont)
}
}
Use as follows:
Text("Some text")
.font(Font(yourUIFont))

Related

Is there a way to set font tracking (i.e. spacing) inside a custom SwiftUI ButtonStyle?

In SwiftUI you can set font tracking (spacing between letters) on a Text View using the tracking View modifier:
Text("Hello, World!")
.tracking(10)
If you have a Button and apply your custom ButtonStyle you can do all kinds of modifications to the style of the button, but since the configuration.label is not an instance of Text but rather ButtonStyleConfiguration.Label, I don't have direct access to apply tracking. Is there a way to do this without having to set it on the Button's label View directly? It seems like something you ought to be able to do since it's a style-related thing but I don't see how to accomplish it.
public struct MyStyle: ButtonStyle {
public func makeBody(configuration: Configuration) -> some View {
configuration.label
// (if I tried to put `.tracking` here it wouldn't work because it isn't Text)
.foregroundColor(Color.red)
.clipShape(Capsule())
// … etc.
}
}
ViewModifier to change both font and tracking looks ~ related but no relevant answer.
Yes, ButtonStyleConfiguration.Label is not matched to Text, but it is your style you can ignore standard label, and request own by interface contract, exactly which you need (Text in this case), like in below demo
public struct MyStyle: ButtonStyle {
let label: Text // << here !!
public func makeBody(configuration: Configuration) -> some View {
label // << here !!
.tracking(10)
.foregroundColor(configuration.isPressed ? .black : .red)
.clipShape(Capsule())
}
}
and use it as (or create own button extension with button builder to hide those unneeded curls)
Button(action: {
// action here
}){}
.buttonStyle(MyStyle(label: Text("Hello")))
Tested with Xcode 13.2 / iOS 15.2

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
}
}

Apply ViewModifier to UIViewRepresentable

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
...
}

SwiftUI - Expand TextField to Fill View

I am trying to create a TextField that spans across the entire window. Currently it only expands to fill the view on the horizontal axis. I'm aiming to achieve something similar to that seen in TextEdit.
Here is my code so far:
struct ContentView: View {
#State var contents: String = "Hello World\nThis is a test.";
var body: some View {
VStack() {
TextField("Notes", text: $contents)
.lineLimit(nil)
}
.frame(height: 600)
}
}
Application Screenshot
Is this possible in SwiftUI or will I need to revert to using legacy AppKit components?
I've found that wrapping an NSTextView is still be the best way to achieve something similar to that seen in TextEdit. So yes, at least now you will have to use legacy AppKit components in your SwiftUI code.
struct MultilineTextView: NSViewRepresentable {
typealias NSViewType = NSTextView
#Binding var text: String
func makeNSView(context: Self.Context) -> Self.NSViewType{
let view = NSTextView()
view.isEditable = true
view.isRulerVisible = true
return view
}
func updateNSView(_ nsView: Self.NSViewType, context: Self.Context) {
nsView.string = text
}
}
struct ContentView: View {
#State var contents: String = "Hello World\nThis is a test.";
var body: some View {
VStack() {
MultilineTextView(text: $contents)
}.background(Color.white)
}
}
It seems, that there is bug with TextField line limit, according to this answer and comments to it.
I believe in this thread you can find answer, how achieve what you want.
Just replying to this as nobody's given a decent answer yet besides wrapping UIKit.
TextEditor(text: $contents)
Is the better path as it WILL behave like you wish as compared to TextField(). It automatically takes care of scrolling and expanding to the View area allowed.
TextEditor WILL require you to be on at least iOS 14. If you're not on iOS 14 then wrapping the UIKit control is your best option.