Check for "real" view behind AnyView - swiftui

Given the following code
import SwiftUI
struct Foo: View {
var body: some View {
Text("Hello World")
}
}
let anyViewFoo = AnyView(Foo())
debugPrint(anyViewFoo)
Is it possible to check foo for the "real" type or e.g. cast to Foo? So something (which obviously doesn't work) like the following would be awesome:
let foo: Foo? = anyViewFoo as? Foo
Does something like this exist?

SwiftUI AnyView does not give access to original view, but we can use our custom to store it and then access.
Note: pay attention - all views are structs, so accessing original view you get a copy (as everywhere in SwiftUI)
Here is a possible approach:
struct AnyMyView: View {
private let internalView: AnyView
let originalView: Any
init<V: View>(_ view: V) {
internalView = AnyView(view)
originalView = view
}
var body: some View {
internalView
}
}
and now a demo of usage based on your original code
struct DemoAnyMyView: View {
let anyViewFoo = AnyMyView(Foo())
var body: some View {
VStack {
// erased type view
anyViewFoo
Divider()
// restored type view
if let foo = anyViewFoo.originalView as? Foo {
foo
}
}
}
}
Prepared & tested with Xcode 12.1 / iOS 14.1

Related

SwiftUI "Cannot find 'ObservableObject' in scope" in function, where the 'ObservableObject' is a parameter

In my function, I have a parameter a manager: TextManager, but when I try to bind its text to a Binding<String>, it says "Cannot find '$manager' in scope". Why cannot it find the 'manager', it is right there in the function parameter?
The main UI code is the following:
struct BugView: View {
#ObservedObject var textManager = TextManager(limit: 30)
var body: some View {
VStack {
Text("line 1")
fetchTextView(title:"Name", manager: textManager)
Text("line 3")
}
}
#ViewBuilder
private func fetchTextView(title: String, manager : TextManager) -> some View {
TextField(title, text: $manager.text) // <- Cannot find '$manager' in scope
Text("\(manager.text.count)/\(manager.characterLimit)")
}
}
where the TextManager is the following
class TextManager: ObservableObject {
let characterLimit: Int
#Published var text = "" {
didSet {
if text.count > characterLimit && oldValue.count <= characterLimit {
text = oldValue
}
}
}
init(limit: Int = 10) {
characterLimit = limit
}
}
I started with this code, and it works fine
struct BugView: View {
#ObservedObject var textManager = TextManager(limit: 30)
var body: some View {
VStack {
Text("line 1")
TextField("good case", text: $textManager.text)
Text("line 3")
}
}
}
I just want to wrap the some text view generation logic inside a function because I would have multiple of those views, and I would like to avoid code duplication.
The first problem is TextManager is a class instead of struct. We only need reference types in SwiftUI if we are doing something asynchronous like saving or syncing data (although .task removes that need for most use cases).
struct TextManager {
let characterLimit: Int
var text = "" {
didSet {
if text.count > characterLimit && oldValue.count <= characterLimit {
text = oldValue
}
}
}
init(limit: Int = 10) {
characterLimit = limit
}
mutating func reset() {
text = ""
}
}
In a struct, any change to its properties, like text is detected as a change to the whole struct, which is a feature that SwiftUI takes advantage of in its design. For logic you might want to test, you can use mutating func.
Next, instead of a #ViewBuilder func you need a custom subview, e.g.
struct FetchTextView: View {
let title: String
#Binding var config: TextManager
var body: some View {
TextField(title, text: $config.text)
Text("\(config.text.count)/\(config.characterLimit)")
}
}
#Binding var instead of let gives us write access to the state, but as with let, body is also called when the value changes. The reason is breaking up the View struct hierarchy into small Views makes SwiftUI more efficient and faster, if you use funcs you risk breaking its dependency tracking (which is how SwiftUI decides if body should be called based on any used values changing). Use it like:
#State var config = TextManager()
FetchTextView(title: title, config: $config)
To use ObservableObject in function, you will have to create manual binding instead of the syntactic sugar $. The following code would work
#ViewBuilder
private func fetchTextView(title: String, manager: TextManager) -> some View {
let binding = Binding(
get: { manager.text },
set: { manager.text = $0 }
)
TextField(title, text: binding)
Text("\(manager.text.count)/\(manager.characterLimit)")
}
why can't we use it like this
just pass textManager directly instead pass via fetchTextView Method
struct BugView: View {
#StateObject var textManager = TextManager(limit: 30) // <-- when creating an instance make sure use StateObject
var body: some View {
VStack {
Text("line 1")
fetchTextView(title:"Name")
Text("line 3")
}
}
#ViewBuilder
private func fetchTextView(title: String) -> some View {
TextField(title, text: $textManager.text) // <-- just use textManagerDirectly...
Text("\(textManager.text.count)/\(textManager.characterLimit)")
}
}

How to execute non-view code inside a SwiftUI view

I have been struggling with this over and over again, so I think I'm missing something. I need to do math, make a setting, assign a value or any of a host of simple operations in reaction to some user action, such as the example shown here, and SwiftUI is wanting a View where I don't need a view. There's got to be a way around the ViewBuilder's rules. I kind of worked around this by creating an unnecessary view and executing the code I need inside the View's init(), but that seems terribly awkward.
import SwiftUI
struct ContentView: View
{
#State var showStuff = false
var body: some View
{
VStack
{
Toggle(isOn: $showStuff)
{
Text("Label")
}
if showStuff
{
UserDefaults.standard.set(true, forKey: "Something")
}
}
}
}
Way 1 (best):
struct ExecuteCode : View {
init( _ codeToExec: () -> () ) {
codeToExec()
}
var body: some View {
return EmptyView()
}
}
usage:
HStack {
ExecuteCode {
print("SomeView1 was re-drawn!")
}
SomeView1()
}
Way 2:
( my first way is better - you're able to write only simple code here )
Code with let _ = works inside of View!
HStack {
let _ = print("SomeView1 was re-drawn!")
SomeView1()
}
Way 3:
( my first way is better - too difficult code structure; But code doings the same )
HStack {
// here is the magic
{ () -> SomeView1() in
// here is code to execute
print("SomeView1 was re-drawn!")
// here is the magic
return SomeView1()
}
}
Views are actually so-called Function Builders, and the contents of the view body are used as arguments to to the buildBlock function, as mentioned by #Asperi.
An alternative solution if you must run code inside this context is using a closure that returns the desired view:
VStack {
// ... some views ...
{ () -> Text in
// ... any code ...
return Text("some view") }()
// ... some views ...
}
In SwiftUI 2.0, there's a new ViewModifier onChange(of:perform:), that allows you to react to changes in values.
But you can create something similar to that with a neat trick (I forgot where I saw it, so unfortunately I can't leave proper attribution), by extending a Binding with onChange method:
extension Binding {
func onChange(perform action: #escaping (Value, Value) -> Void) -> Self {
.init(
get: { self.wrappedValue },
set: { newValue in
let oldValue = self.wrappedValue
DispatchQueue.main.async { action(newValue, oldValue) }
self.wrappedValue = newValue
})
}
}
You can use it like so:
Toggle(isOn: $showStuff.onChange(perform: { (new, old) in
if new {
UserDefaults.standard.set(true, forKey: "Something")
}
}))
You cannot do what you try to do, because actually every view block inside body is a ViewBuidler.buildBlock function arguments. Ie. you are in function arguments space. I hope you would not expect that expression like
foo(Toggle(), if showStuff { ... } )
would work (assuming foo is func foo(args: View...). But this is what you try to do in body.
So expressions in SwiftUI have to be out of ViewBuilder block (with some exceptions which ViewBuilder itself supports for views).
Here is a solution for your case:
SwiftUI 2.0
struct ContentView: View {
#AppStorage("Something") var showStuff = false
var body: some View {
VStack {
Toggle(isOn: $showStuff) {
Text("Label")
}
}
}
}
SwiftUI 1.0
Find in already solved SwiftUI toggle switches
Note: View.body (excluding some action modifiers) is equivalent of UIView.draw(_ rect:)... you don't store UserDefaults in draw(_ rect:), do you?

Cannot convert value of type 'UIHostingController<AnyView>' to closure result type 'UIHostingController<Page>'

I'm making a PageView implementation with SwiftUI according to the Interacing with UIKit tutorial.
And, particularly, there is such code:
struct PageView<Page: View>: View {
var viewControllers: [UIHostingController<Page>]
#State var currentPage = 0
init(_ views: [Page]) {
self.viewControllers = views.map { UIHostingController(rootView: $0) }
}
var body: some View {
PageViewController(controllers: viewControllers, currentPage: $currentPage)
}
}
The View may be called in a simple way:
PageView([FirstView(), SecondView()])
I want to make nice paddings around the page views, so I can call this view in following manner:
PageView([AnyView(FirstView().padding()), AnyView(SecondView().padding()])
All is good, but I have to repeat every time the .padding() and wrapping in the AnyView in caller method. However, when I try to move this code to the init method, I'm trapped in the error, I can't get around:
init(_ views: [Page]) {
self.viewControllers = views.map { UIHostingController(rootView: AnyView($0.padding())) }
}
which is reported as Cannot convert value of type 'UIHostingController<AnyView>' to closure result type 'UIHostingController<Page>'
Any hint how to address it?
Probably the simplest would be to add padding
var body: some View {
PageViewController(controllers: viewControllers, currentPage: $currentPage)
.padding() // << here !!
}
but if you want to make those AnyViews in init, then change declaration of member as
var viewControllers: [UIHostingController<AnyView>] // << here !!

Is there a way decouple views from view models like the following?

My target is 2 thing:
1. to make a view depending on a view model protocol not a concrete class.
2. a sub view gets the view model from the environment instead of passing it through the view hierarchy
I've mentioned my goals so if there's a totally different way to achieve them, I'm open to suggestion.
Here's what've tried and failed of course and raised weird error:
struct ContentView: View {
var body: some View {
NavigationView {
MyView()
}
}
}
struct MyView: View {
#EnvironmentObject var viewModel: some ViewModelProtocol
var body: some View {
HStack {
TextField("Enter something...", text:$viewModel.text)
Text(viewModel.greetings)
}
}
}
//MARK:- View Model
protocol ViewModelProtocol: ObservableObject {
var greetings: String { get }
var text: String { get set }
}
class ConcreteViewModel: ViewModelProtocol {
var greetings: String { "Hello everyone..!" }
#Published var text = ""
}
//MARK:- Usage
let parent = ContentView().environmentObject(ConcreteViewModel())
Yes there is, but it's not very pretty.
You're running into issues, since the compiler can't understand how it's ever supposed to infer what type that that some protocol should be.
The reason why some works in declaring your view, is that it's inferred from the type of whatever you supply to it.
If you make your view struct take a generic viewmodel type, then you can get this up and compiling.
struct MyView<ViewModel: ViewModelProtocol>: View {
#EnvironmentObject var viewModel: ViewModel
var body: some View {
Text(viewModel.greetings)
}
}
the bummer here, is that you now have to declare the type of viewmodel whenever you use this view, like so:
let test: MyView<ConcreteViewModel> = MyView()

How to print() to Xcode console in SwiftUI?

So I tried to put a print statement while debugging in a SwiftUI View.
print("landmark: \(landmark)")
In the following body.
var body: some View {
NavigationView {
List {
Toggle(isOn: $userData.showFavoritesOnly) {
Text("Favorite only")
}
ForEach(landmarkData) { landmark in
print("landmark: \(landmark)")
if !self.userData.showFavoritesOnly || landmark.isFavorite {
NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
Compiler errors out:
So, what is the proper way to print to console in SwiftUI?
EDIT:
I made Landmark conform to CustomStringConvertible:
struct Landmark: Hashable, Codable, Identifiable, CustomStringConvertible {
var description: String { name+"\(id)" }
var id: Int
var name: String
.....
I still get the "String is not convertible to any" error. Should it work now?
You can easily add a print statement anywhere in a function builder by simply storing its return value in a wildcard, effectively ignoring it:
let _ = print("hi!")
No setup or other verbosity needed!
Why does this work while a regular print() doesn't?
The way SwiftUI's #ViewBuilder (and result builders in general) is that they consume any values in a closure that aren't used otherwise (e.g. if you just have 42 on its own line). The print function returns Void (nothing), which the builder would have to build into a view, so it fails. By instead assigning it to a variable (in this case _, basically a variable that you can never access), the Void is never offered to the view builder in the first place.
You could argue the builder should simply accept and ignore Void values, but the idea is that your builder closures should not have side effects (I'd remove print statements after finishing debugging too)—you should not rely on these closures being called at certain times.
Here's a helper Print( ... ) View that acts like a print( ... ) function but within a View
Put this in any of your view files
extension View {
func Print(_ vars: Any...) -> some View {
for v in vars { print(v) }
return EmptyView()
}
}
and use inside of body like so
Print("Here I am", varOne, varTwo ...)
or inside a ForEach {} like so
self.Print("Inside ForEach", varOne, varTwo ...)
Note: you might need to put Print() into a Group {} when combining with existing views
Try right-clicking on the live preview play button and selecting 'Debug Preview from the popup
You can print in the body structure but to do so you have to explicitly return the view you want to render. The body property inside a View is just a computed property like any other in Swift that implicitly returns the view. And just like any other computed property, you can perform operations inside the computed property as long as a value is explicitly returned. For example, this will throw an error when you try to print because there is no explicit return:
struct SomeView: View {
#State var isOpen = false
var body: some View {
print(isOpen) // error thrown here
VStack {
// other view code
}
}
}
But if we explicitly return the view we want then it will work e.g.
struct SomeView: View {
#State var isOpen = false
var body: some View {
print(isOpen) // this ok because we explicitly returned the view below
// Notice the added 'return' below
return VStack {
// other view code
}
}
}
The above will work well if you're looking to view how state or environment objects are changing before returning your view, but if you want to print something deeper down within the view you are trying to return, then I would go with #Rok Krulec answer.
It is possible to use print() remembering that all SwiftUI View content are (a) implicit closures and (b) it is highly recommended to decompose views as much as possible to have simple structure, so it might look like the following...
struct Model: Identifiable {
let value: String
var id: String {
value
}
init (_ value: String) {
self.value = value
}
}
struct TestView: View {
#State var showFavoritesOnly = false
#State var listData: [Model] = [Model("one"), Model("two"), Model("three")]
var body: some View {
NavigationView {
List {
Toggle(isOn: $showFavoritesOnly) {
Text("Favorite only")
}
ForEach(listData) { data in
self.rowView(data: data)
}
}
}
}
private func rowView(data: Model) -> some View {
#if DEBUG
print(">> \(data.value)")
#endif
return NavigationLink(destination: Text("Details")) {
Text("Go next from \(data.value)")
}
}
}
... and right clicking in Preview to select run as Debug Preview we get:
2019-10-31 14:28:03.467635+0200 Test[65344:11155167] [Agent] Received connection, creating agent
2019-10-31 14:28:04.472314+0200 Test[65344:11155168] [Agent] Received display message
>> one
>> two
>> three
You can declare a printing() method that includes print() and returns EmptyView struct.
struct ContentView: View {
#State private var offset = CGSize.zero
func printing(_ items: Any...) -> some View {
let _ = print(items)
return EmptyView()
}
var body: some View {
#if DEBUG
printing(offset) // prints [(0.0, 0.0)]
#endif
ZStack {
Text("Hello")
}
}
}
The safest and easiest way to print while debugging in a SwiftUI View.
extension View {
func Print(_ item: Any) -> some View {
#if DEBUG
print(item)
#endif
return self
}
}
Usage Example:
struct ContentView: View {
var body: some View {
VStack {
ForEach((1...5), id: \.self) { number in
Text("\(number)")
.Print(number)
}
}
}
}
Console output:
1
2
3
4
5
It can be generalized to:
extension View {
func Perform(_ block: () -> Void) -> some View {
block()
return EmptyView()
}
}
So in your example:
ForEach(landmarkData) { landmark in
Perform { print("landmark: \(landmark)") }
if !self.userData.showFavoritesOnly || landmark.isFavorite {
NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
Here you go. It will just work like simple print but inside a view.
func printv( _ data : Any)-> EmptyView{
print(data)
return EmptyView()
}
and use it like that
struct ContentView: View {
var body: some View {
VStack() {
Text("hello To SwiftUI")
printv("its easy to code in SwiftUI")
Text("And Good to have you here")
}
}
}
The following extension on View is as intuitive as print because it's made to replicate the default print(_:separator:terminator:) function signature & behavior.
extension View {
func printUI(_ args: Any..., separator: String = " ", terminator: String = "\n") -> EmptyView {
let output = args.map(String.init(describing:)).joined(separator: separator)
print(output, terminator: terminator)
return EmptyView()
}
}
Usage Example:
struct ContentView: View {
var body: some View {
VStack {
printUI("ContentView", "1")
printUI("ContentView", "2", separator: ", ", terminator: "\n.\n.\n")
printUI("ContentView", "3", separator: "; ")
Text("Hello, World!")
}
}
}
Console Output:
ContentView 1
ContentView, 2
.
.
ContentView; 3
EDIT: Debug Preview is no longer supported in the latest versions of Xcode.
Very easy way to debug your Preview:
Open your Swift project in Xcode 11.
Right-click (or Control-click) on the Live Preview button in the bottom right corner of the preview.
Select Debug Preview.
How to debug your SwiftUI previews in Xcode
// Try this, add a 'return' on a view then the 'print' can stay alive in.
struct ContentView: View {
var num: Int = 1
var body: some View {
print(num)
return Text("hello")
}
}
You can't because you're in a computed property. You need for example a button and in the action you define the print. Or work with breakpoints
You can not print in body structure i.e. a structure which is some view type.For print you need to make function out of body structure and call it using button or something else.
This should work
if true {
print(aVar, "xx")
}
return ZStack {
...
}