Fit content mode for a custom view in SwiftUI - swift

How can I set content mode as fit for a custom UIImageView wrapped in SwiftU?
struct CustomView: UIViewRepresentable {
func makeUIView(context: Context) -> UIImageView {
CustomUIImage()
}
func updateUIView(_ uiView: UIImageView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
CustomView()
}
}
In the code above, CustomUIImage is a subclass of UIImageView from UIKit. It's wrapped in UIViewRepresentable to integrate it with the SwiftUI framework. Calling CustomView().aspectRatio(contentMode: .fit) didn't work in this case

Here is possible approach
struct CustomView: UIViewRepresentable {
private let imageView = CustomUIImage()
init(contentMode: UIView.ContentMode = .center) {
imageView.contentMode = contentMode
}
func makeUIView(context: Context) -> UIImageView {
imageView
}
func updateUIView(_ uiView: UIImageView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
CustomView(contentMode: .scaleAspectFit)
}
}

Related

How to add line numbers to UITextView in Swift (structure of app built using SwiftUI)

I am making a code editor for iPad (as a fun little project). I am trying to add line numbers to a UITextView that is in the SwiftUI code via a UIViewRepresentable. Any ideas on how to go about this? I should mention, I use an NSAttributedString for syntax hilighting. Here is a paired down version of my code (to fit better).
The view that references the UITextView
struct TestEditorView: View {
#State var text = NSAttributedString(string: "")
#State var selection: String?
var body: some View {
NavigationView {
VStack {
if let _ = selection {
ScrollView {
HStack {
TextView(text: $text)
.padding(.horizontal, 5)
}
}
}
}
}
}
}
The UITextView
struct TextView: UIViewRepresentable {
#Binding var text: NSAttributedString
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.attributedText = TextFormatter.format(text.string)
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = TextFormatter.format(text.string)
}
func makeCoordinator() -> TextViewCoordinator {
TextViewCoordinator(text: $text)
}
}
class TextViewCoordinator: NSObject, UITextViewDelegate {
#Binding private var text: NSAttributedString
init(text: Binding<NSAttributedString>) {
self._text = text
}
func textViewDidChange(_ textView: UITextView) {
text = TextFormatter.format(textView.attributedText.string)
}
}
P.S. I do not believe this applies because it is in objective-c and does not interface with SwiftUI.

SwiftUI: UITextView text does not wrap when inside a ForEach/LazyVGrid (Self-sizing/Dynamic height UITextView)

The issue
What I'm creating is a LazyVGrid with many UITextView as "cells" inside, but the text does not wrap when it exceeds the parent width.
When the text is a single line, all is fine:
But when the text reaches the limit, something weird happens:
What I want to achieve is simply that the text wraps, can anyone help me?
The code
To have more flexibility, I chose to use a UIKit UITextView created using the UIViewRepresentable protocol, rather than using the new TextEditor provided by SwiftUI:
struct TextView: UIViewRepresentable {
#Binding private var text: String
init(text: Binding<String>) {
self._text = text
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.isScrollEnabled = false
textView.backgroundColor = .systemIndigo // debug
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
context.coordinator.parent = self
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// MARK: - Coordinator
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextView
init(_ parent: TextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
}
}
This is my SwiftUI View:
struct ContentView: View {
private let columns: [GridItem] = Array(repeating: .init(.flexible(), spacing: 0.0), count: 1)
#ObservedObject private var viewModel: ContentViewModel
init(viewModel: ContentViewModel) {
self.viewModel = viewModel
}
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach($viewModel.blocks) { $block in
TextView(text: $block.content)
.frame(minHeight: 24.0)
}
}
.padding(.horizontal, 24.0)
}
}
}
I'll skip the ViewModel part because it's not relevant.
I've already seen and tried this Dynamic row hight containing TextEditor inside a List in SwiftUI, without success
Thanks everyone in advance ;)

How can I make a generic UIViewRepresentable struct?

I want build a struct that it takes a UI type as input and it present that UI in SwiftUI, for example I have this down codes for UILabel and UIButton, I want make a generic one to free me from making an individual struct for each UI from UIKit:
struct UILabelViewRepresentable: UIViewRepresentable {
let configuration: (UILabel) -> ()
func makeUIView(context: Context) -> UILabel {
return UILabel()
}
func updateUIView(_ uiView: UILabel, context: Context) {
configuration(uiView)
}
}
struct UIButtonViewRepresentable: UIViewRepresentable {
let configuration: (UIButton) -> ()
func makeUIView(context: Context) -> UIButton {
return UIButton()
}
func updateUIView(_ uiView: UIButton, context: Context) {
configuration(uiView)
}
}
Here what I tried so far:
struct GenericUIViewRepresentable<UIViewType: UIView>: UIViewRepresentable {
let uiViewType: UIViewType
let configuration: (UIViewType) -> ()
func makeUIView(context: Context) -> UIViewType {
return uiViewType
}
func updateUIView(_ uiView: UIViewType, context: Context) {
configuration(uiView)
}
}
it makes an error when I want use it:
Cannot convert value of type 'UILabel.Type' to expected argument type 'UIView'
So I know about the error, I want make my code works and it makes me free to type all UIKit UI that I want.
use case:
struct ContentView: View {
var body: some View {
GenericUIViewRepresentable(uiViewType: UILabel, configuration: { label in
label.text = "Hello, World!"
})
}
}
You want to pass in the UIView type, not just a UIView instance. This means you want to do UIViewType.Type rather than UIViewType.
Code:
struct GenericUIViewRepresentable<UIView: UIKit.UIView>: UIViewRepresentable {
let uiViewType: UIView.Type
let configuration: (UIView) -> ()
func makeUIView(context: Context) -> UIView {
uiViewType.init()
}
func updateUIView(_ uiView: UIView, context: Context) {
configuration(uiView)
}
}
Usage:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello world!")
GenericUIViewRepresentable(uiViewType: UILabel.self) { label in
label.text = "Other text!"
}
}
}
}
Here on swift.org is where you can learn more about metatype types.

SwiftUI Button on top of a MKMapView does not get triggered

I have a button on top of a MKMapView. But the button does not get triggered when it's tapped on. Do you know what's missing?
MapView.swift
import SwiftUI
import UIKit
import MapKit
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let mkMapView = MKMapView()
return mkMapView
}
func updateUIView(_ uiView: MKMapView, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator: NSObject, MKMapViewDelegate { }
}
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
MapView()
Button(action: {
print("Tapped")
}) {
Image(systemName: "lock.fill")
}
}
}
}
Give it just a bit more internal space to be better recognizable. Here is fixed & tested variant (Xcode 12 / iOS 14):
struct TestButtonWithMap: View {
#State private var locked = true
var body: some View {
ZStack {
MapView()
Button(action: {
print("Tapped")
self.locked.toggle()
}) {
Image(systemName: locked ? "lock.fill" : "lock.open")
.padding() // << here !!
}
}
}
}

View content doesn't appear in UIScrollView

I have this...
func makeUIView(context: Context) -> UIScrollView {
let control = UIScrollView()
control.addSubview(UIHostingController(rootView: preview).view)
return control
}
If I put my preview view outside of my custom scroll view then I can see it. But when I add it as a subview of a UIScrollView then I can't see anything.
Have I added it correctly?
Here is the complete code for the scroll view, which I got from here.
import SwiftUI
import Foundation
struct LegacyScrollView : UIViewRepresentable {
var preview: AnyView
init(preview: AnyView) {
self.preview = preview
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIScrollView {
let control = UIScrollView()
control.addSubview(UIHostingController(rootView: preview).view)
return control
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
}
class Coordinator: NSObject {
var control: LegacyScrollView
init(_ control: LegacyScrollView) {
self.control = control
}
#objc func handleRefreshControl(sender: UIRefreshControl) {
sender.endRefreshing()
}
}
}