Intrinsic height for wrapped non scrolling UITextView in SwiftUI ScrollView - swiftui

I'm working with a wrapped UITextView via UIViewRepresentable. This textView is supposed to be as high es required, based on its content, i.e. the (attributed)string.
struct TextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.isScrollEnabled = false
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}
This works fine:
struct TextView_Previews: PreviewProvider {
static var previews: some View {
TextView(text: .constant("some multiline\na\nb\nc\nd\nlorem ipsum"))
}
}
Doesn't work
struct TextView_Previews: PreviewProvider {
static var previews: some View {
ScrollView { // <- when placed inside ScrollView the height is reduced to one line
TextView(text: .constant("some multiline\na\nb\nc\nd\nlorem ipsum"))
}
}
}
How can this be fixed?

struct TextView_Previews: PreviewProvider {
static var previews: some View {
ScrollView { // <- when placed inside ScrollView the height is reduced to one line
TextView(text: .constant("some multiline\na\nb\nc\nd\nlorem ipsum"))
.frame(minHeight: 100)
}
}
}

Related

TextView doesn't synchronize the binding variable in SwiftUI

I created a simple TextView like this:
import SwiftUI
struct TextView: UIViewRepresentable {
#Binding var inputText: String
func makeUIView(context: UIViewRepresentableContext<Self>) -> UITextView {
return UITextView()
}
func updateUIView(_ uiView: UITextView, context: Context) {
// uiView.text = self.inputText
self.inputText = uiView.text
}
}
And the main view contains a variable with a #State wrapper.
import SwiftUI
struct Test: View {
#State private var inputText: String = ""
var body: some View {
VStack {
TextEditor(text: $inputText)
TextView(inputText: $inputText)
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
As I understand, the TextView and TextEditor are connected by this inputText, who has a #Binding wrapper in the definition of TextView. Thus if I type in one of them, another should show the same text. However, to my surprise, the text in these two are completely separated with each other.
If I change the self.inputText = uiView.text to uiView.text = self.inputText, then when I type in TextEditor, the TextView would show the same text, but if I type in TextView, then nothing happens in TextEditor.
Why does this happen?

Swift detect changes to a variable from another view within a view

I have the following Class
class GettingData: ObservableObject {
var doneGettingData = false
{
didSet {
if doneGettingData {
print("The instance property doneGettingData is now true.")
} else {
print("The instance property doneGettingData is now false.")
}
}
}
}
And I'm updating the variable doneGettingData within a mapView structure that then I'm using in my main view (contentView).
This variable its changing from false / true while the map gets loaded and I can detecte it from the print that I have used in the class so I know it's changing.
I want to use it to trigger a spinner within ContentView where I have the following code:
import SwiftUI
import Combine
struct ContentView: View {
var done = GettingData().doneGettingData
var body: some View {
VStack {
MapView().edgesIgnoringSafeArea(.top)
Spacer()
Spinner(isAnimating: done, style: .large, color: .red)
}
}
struct Spinner: UIViewRepresentable {
let isAnimating: Bool
let style: UIActivityIndicatorView.Style
let color: UIColor
func makeUIView(context: UIViewRepresentableContext<Spinner>) -> UIActivityIndicatorView {
let spinner = UIActivityIndicatorView(style: style)
spinner.hidesWhenStopped = true
spinner.color = color
return spinner
}
func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<Spinner>) {
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
What should I do in order to the variable to change as it is changing with the print but inside the view ? I have tried many options but nothing works !
Thank you
Make your property published
class GettingData: ObservableObject {
#Published var doneGettingData = false
}
then make data observed and pass that instance into MapView for use, so modify that property internally
struct ContentView: View {
#ObservedObject var model = GettingData()
var body: some View {
VStack {
MapView(dataGetter: model).edgesIgnoringSafeArea(.top)
Spacer()
Spinner(isAnimating: model.doneGettingData, style: .large, color: .red)
}
}

Access #Environment object from UIViewControllerRepresentable object

I used this approach to incorporate camera with swiftUI:
https://medium.com/#gaspard.rosay/create-a-camera-app-with-swiftui-60876fcb9118
The UIViewControllerRepresentable is implemented by PageFourView class. PageFourView is one of the TabView of the parental View. I have an #EnvironmentObject passed from the SceneDelegate to the parent view and then to PageFourView. But when I am trying to acess #EnvironmentObject from makeUIViewController method of PageFourView I get an error:
Fatal error: No ObservableObject of type Data found. A
View.environmentObject(_:) for Data may be missing as an ancestor of
this view
... even though I can see the #Environment object from context.environment. Here is my code:
import UIKit
import SwiftUI
import Combine
final class PageFourView: UIViewController, UIViewControllerRepresentable {
public typealias UIViewControllerType = PageFourView
#EnvironmentObject var data: Data
var previewView: UIView!
override func viewDidLoad() {
previewView = UIView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
previewView.contentMode = UIView.ContentMode.scaleAspectFit
view.addSubview(previewView)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<PageFourView>) -> PageFourView {
print(context.environment)
print(self.data.Name)
return PageFourView()
}
func updateUIViewController(_ uiViewController: PageFourView, context: UIViewControllerRepresentableContext<PageFourView>) {
}
}
struct PageFourView_Previews: PreviewProvider {
#State static var data = Data()
static var previews: some View {
PageFourView().environmentObject(self.data)
}
}
here is the parental view that PageFourView is called from:
import SwiftUI
struct AppView: View {
#EnvironmentObject var data: Data
var body: some View {
TabView {
PageOneView().environmentObject(data)
.tabItem {
Text("PageOne")
}
PageTwoView().environmentObject(data)
.tabItem {
Text("PageTwo")
}
PageThreeView().environmentObject(data)
.tabItem {
Text("PageThree")
}
PageFourView().environmentObject(data)
.tabItem {
Text("PageFour")
}
}
}
}
struct AppView_Previews: PreviewProvider {
#State static var data = Data()
static var previews: some View {
AppView().environmentObject(self.data)
}
}
final class CameraViewController: UIViewController {
let cameraController = CameraController()
var previewView: UIView!
override func viewDidLoad() {
previewView = UIView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
previewView.contentMode = UIView.ContentMode.scaleAspectFit
view.addSubview(previewView)
cameraController.prepare {(error) in
if let error = error {
print(error)
}
try? self.cameraController.displayPreview(on: self.previewView)
}
}
}
extension CameraViewController : UIViewControllerRepresentable{
public typealias UIViewControllerType = CameraViewController
public func makeUIViewController(context: UIViewControllerRepresentableContext<CameraViewController>) -> CameraViewController {
return CameraViewController()
}
public func updateUIViewController(_ uiViewController: CameraViewController, context: UIViewControllerRepresentableContext<CameraViewController>) {
}
}
And UIViewRepresentable and UIViewControllerRepresentable is-a View and must be a struct.
In described case controller representable is not needed, because you operate with view, so here is corrected code:
struct PageFourView: UIViewRepresentable {
#EnvironmentObject var data: Data
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width,
height: UIScreen.main.bounds.size.height))
view.contentMode = UIView.ContentMode.scaleAspectFit
print(context.environment)
print(self.data.Name)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
btw, you don't need to pass .environmentObject to subviews in same view hierarchy, only for new hierarchy, like sheets, so you can use simplified code as below
var body: some View {
TabView {
PageOneView()
.tabItem {
Text("PageOne")
}
PageTwoView()
.tabItem {
Text("PageTwo")
}
PageThreeView()
.tabItem {
Text("PageThree")
}
PageFourView()
.tabItem {
Text("PageFour")
}
}
}
Update: for CameraViewController just wrap it as below
struct CameraView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> CameraViewController {
CameraViewController()
}
func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {
}
}

EnvironmentVariables not working when passing variable from one view to another in SwiftUI

I have found a few similar examples of how to pass variables among multiple views in SwiftUI:
Hacking with Swift - How to use #EnvironmentObject to share data between views
How to pass variable from one view to another in SwiftUI
I am trying to follow the examples and use EnvironmentVariables and modify the ContentView where it's first defined in the SceneDelegate. However, when trying both examples, I get the error "Compiling failed: 'ContentView_Previews' is not a member type of 'Environment'". I am using Xcode Version 11.3.1.
Following the example given in How to pass variable from one view to another in SwiftUI, here is code contained in ContentView:
class SourceOfTruth: ObservableObject{
#Published var count = 0
}
struct ContentView: View {
#EnvironmentObject var truth: SourceOfTruth
var body: some View {
VStack {
FirstView()
SecondView()
}
}
}
struct FirstView: View {
#EnvironmentObject var truth: SourceOfTruth
var body: some View {
VStack{
Text("\(self.truth.count)")
Button(action:
{self.truth.count = self.truth.count-10})
{
Text("-")
}
}
}
}
struct SecondView: View {
#EnvironmentObject var truth: SourceOfTruth
var body: some View {
Button(action: {self.truth.count = 0}) {
Text("Reset")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(SourceOfTruth())
}
}
... and here is the contents of SceneDelegate:
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var truth = SourceOfTruth() // <- Added
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(SourceOfTruth())) // <- Modified
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
}
func sceneDidBecomeActive(_ scene: UIScene) {
}
func sceneWillResignActive(_ scene: UIScene) {
}
func sceneWillEnterForeground(_ scene: UIScene) {
}
func sceneDidEnterBackground(_ scene: UIScene) {
}
}
I does not depend on Xcode version and it is not an issue. You have to set up ContentView in ContentView_Previews in the same way as you did in SceneDelegate, provide .environmentObject, as in below example
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(_Your_object_here())
}
}

SwiftUI - List - UIViewRepresantable won't be updated

In my list, my UIViewRepresentable won't be updated if it is the only item in list. If I add e.g. a Text to it, it works. To see the effect, scroll down and up again.
What am i doing wrong?
Hers is my code:
struct Test : UIViewRepresentable {
var text : String
func makeUIView(context: UIViewRepresentableContext<Test>) -> UILabel {
UILabel()
}
func updateUIView(_ uiView: UILabel, context: UIViewRepresentableContext<Test>) {
uiView.text = text
}
typealias UIViewType = UILabel
}
class Data : ObservableObject {
#Published var names = UIFont.familyNames
}
struct ContentView : View {
#EnvironmentObject var data : Data
var body: some View {
VStack {
List(data.names, id: \.self) { name in
Test(text: name)
// Text(name) // as soon as you comment this out, it works
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
For now I know variant/workaround that works (tested on all available Xcode 11.x)
var body: some View {
VStack {
List(data.names, id: \.self) { name in
Test(text: name).id(name) // << here !!
}
}
}
Note: it might be performance drop on very big lists, but in most usual cases it is not remarkable.
PS: By the way, about class Data - don't name your classes with same names as system one, there might be confuses and unexpectable issues.