I am trying to accept many different types of parameters (could be Int, String or something else) for this okClicked function inside ParentView.
struct ParentView: View {
func okClicked<T>(userAnswer: GeneralAnswer<T>) {
}
var body: some View {
ChildView(okClicked: okClicked)
}
}
struct GeneralAnswer<T> {
var value: T
}
This function actually works as expected. But now I need to pass this function to ChildView. How would the parameter be defined in ChildView?
struct ChildView: View {
var okClicked: (GeneralAnswer<T>) -> () //does not compile
var okClicked2<T>: (GeneralAnswer<T>) -> () //does not compile
var body: some View {
...
}
}
Figured it out:
struct ParentView: View {
func okClicked<T>(userAnswer: GeneralUserAnswer<T>) {
}
var body: some View {
ChildView<Int>(okClicked: okClicked)
}
}
struct ChildView<T>: View {
//typealias T = MyType
var okClicked: (GeneralUserAnswer<T>) -> ()
var body: some View {
Text("")
}
}
Related
I am attempting to pass a Binding through my NavigationStack enum into my View. I'm not sure if I can pass Binding into an enum, if I cannot then how should I go about this. Thanks in advance!
#available(iOS 16.0, *)
enum Route: Hashable, Equatable {
//ERROR HERE: Not sure how to get Binding in enum or if possible
case gotoBView(input: Binding<String>)
#ViewBuilder
func view(_ path: Binding<NavigationPath>) -> some View{
switch self {
case .gotoBView(let input): BView1(bvar: input)
}
}
var isEmpty: Bool {
return false
}
}
//START VIEW
#available(iOS 16.0, *)
struct ContentView25: View {
#State var input = "Hello"
#State var path: NavigationPath = .init()
var body: some View {
NavigationStack(path: $path){
NavigationLink(value: Route.gotoBView(input: $input), label: {Text("Go To A")})
.navigationDestination(for: Route.self){ route in
route.view($path)
}
}
}
}
//View to navigate to with binding
#available(iOS 16.0, *)
struct BView1: View {
#Binding var bvar: String
var body: some View {
Text(bvar)
}
}
Here is a workaround, in previous iOS versions this has dismissed the NavigationLink, In iOS 16.2 it does not behave this way, I would do extensive testing before using this in a production app.
import SwiftUI
#available(iOS 16.0, *)
enum Route: Hashable, Equatable {
case gotoBView(input: Binding<String>)
#ViewBuilder
func view(_ path: Binding<NavigationPath>) -> some View{
switch self {
case .gotoBView(let input): BView1(bvar: input)
}
}
//Create a custom implementation of Hashable that ignores Binding
func hash(into hasher: inout Hasher) {
switch self {
case .gotoBView(let input):
hasher.combine(input.wrappedValue)
}
}
//Create a custom implementation of Equatable that ignores Binding
static func == (lhs: Route, rhs: Route) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
SwiftUI is all about identity and NavigationPath uses Hashable and Equatable to function. This bypasses SwiftUI's implementation.
With the stack, you don't need an enum, you can have multiple navigationDestination for each value type. To use it with a binding you can do it the old way, with a func that looks it up by ID, like how we did it before ForEach supported bindings, e.g.
struct NumberItem: Identifiable {
let id = UUID()
var number: Int
var text = ""
}
struct ContentView: View {
#State var numberItems = [NumberItem(number: 1), NumberItem(number: 2), NumberItem(number: 3), NumberItem(number: 4), NumberItem(number: 5)]
var body: some View {
NavigationStack {
List {
ForEach(numberItems) { numberItem in
NavigationLink(value: numberItem.id) {
Text("\(numberItem.number)")
}
}
}
.navigationDestination(for: NumberItem.ID.self) { numberItemID in
ChildDetailView(numberItems: $numberItems, numberItemID: numberItemID)
}
//.navigationDestination(for: AnotherItem.ID.self) { anotherItemID in
// ...
//}
}
}
}
// this wrapper View was originally needed to make bindings in
// navigationDestinations work at all, now its needed to fix a bug
// with the cursor jumping to the end of a text field which is
// using the binding.
struct ChildDetailView: View {
#Binding var numberItems: [NumberItem]
let numberItemID: UUID
var body: some View {
ChildDetailView2(numberItem: binding(for: numberItemID))
}
private func binding(for numberItemID: UUID) -> Binding<NumberItem> {
guard let index = numberItems.firstIndex(where: { $0.id == numberItemID }) else {
fatalError("Can't find item in array")
}
return $numberItems[index]
}
}
struct ChildDetailView2: View {
#Binding var numberItem: NumberItem
var body: some View {
VStack {
Text("\(numberItem.number)")
TextField("Test", text: $numberItem.text) // cursor jumps to the end if not wrapped in an extra View like this one is.
Button {
numberItem.number += 10
} label: {
Text("Add 10")
}
}
.navigationTitle("Detail")
}
}
I have a view like this:
struct View1: View {
#Binding var myVariable: Bool
init() {
_myVariable = Binding.constant(true) // It works but myVariable is immutable, so I can't edit myVariable
}
init(myVariable: Binding<Bool>) {
_myVariable = myVariable
}
var body: some View {
Button("Change") {
myVariable.toggle()
}
}
}
struct View2: View {
var body: some View {
View1()
}
}
struct View3: View {
#State var myVariable = false
var body: some View {
View1(myVariable: $myVariable)
}
}
And I want to make this: If there is a parameter provided, set this to myVariable like second init in View1. Else, set the first value of myVariable like in first init.
I tried to use Binding.constant(value) but it is immutable. And I can't edit the variable. So, I need a mutable Binding initializer like Binding.constant(value). But I can't find it.
How can I solve this problem?
To avoid over-complicating View1, you can create an intermediate view with that name, and then have a private 'internal' view which has the actual implementation.
Code:
private struct View1Internal: View {
#Binding var myVariable: Bool
var body: some View {
Button("Change") {
myVariable.toggle()
}
}
}
struct View1: View {
private enum Kind {
case state
case binding(Binding<Bool>)
}
#State private var state = true
private let kind: Kind
init() {
kind = .state
}
init(myVariable: Binding<Bool>) {
kind = .binding(myVariable)
}
var body: some View {
switch kind {
case .state: View1Internal(myVariable: $state)
case .binding(let binding): View1Internal(myVariable: binding)
}
}
}
I am following the example of this project to create my iOS app (thanks Alexey!), but can't get the #Environment variable to receive the value that is being passed down the UI hierarchy. The top level view receives the correct value, but the downstream view receives the default value.
EDIT: After tying to replicate Asperi's code, I found that this behavior happens only when the downstream view is invoked via a NavigationLink. Updated the code below:
EDIT2: The problem was with where the environment method was being invoked. Invoking it on the NavigationView instead of the MainView solved the problem. Code updated below:
Custom Environment key - DIContainer
struct DIContainer: EnvironmentKey {
let interactor: Interactor
init(interactor: Interactor) {
self.interactor = interactor
}
static var defaultValue: Self { Self.default }
private static let `default` = Self(interactor: .stub)
}
extension EnvironmentValues {
var injected: DIContainer {
get { self[DIContainer.self] }
set { self[DIContainer.self] = newValue }
}
}
App struct
private let container: DIContainer
init() {
container = DIContainer(interactor: RealInteractor())
}
var body: some Scene {
WindowGroup {
NavigationView {
MainView()
}
.environment(\.injected, container)
}
Main View
struct MainView: View {
#Environment(\.injected) private var injected: DIContainer
// `injected` has the `RealInteractor`, as expected
var body: some View {
VStack {
Text("Main: \(injected.foo())") \\ << Prints REAL
NavigationLink(destination: SearchView()) {
Text("Search")
}
}
}
}
Search View
struct SearchView: View {
#Environment(\.injected) private var injected: DIContainer
// `injected` has the `StubInteractor`, why?
var body: some View {
Text("Search: \(injected.foo())")
}
}
I am able to solve this problem by modifying the MainView like so:
var body: some View {
SearchView()
.environment(\.injected, container)
}
But isn't avoiding doing this repeatedly the purpose of #Environment?
Any guidance/pointers appreciated.
I've tryied to replicate all parts and to make them compiled... and the result just works as expected - environment is passed down the view hierarchy, so you might miss something in your real code.
Here is complete module, tested with Xcode 12.4 / iOS 14.4
class Interactor { // << replicated !!
static let stub = Interactor()
func foo() -> String { "stub" }
}
class RealInteractor: Interactor { // << replicated !!
override func foo() -> String { "real" }
}
struct ContentView: View { // << replicated !!
private let container: DIContainer
init() {
container = DIContainer(interactor: RealInteractor())
}
var body: some View {
NavigationView {
MainView()
}
.environment(\.injected, container) // << to affect any links !!
}
}
// no changes in env parts
struct DIContainer: EnvironmentKey {
let interactor: Interactor
init(interactor: Interactor) {
self.interactor = interactor
}
static var defaultValue: Self { Self.default }
private static let `default` = Self(interactor: .stub)
}
extension EnvironmentValues {
var injected: DIContainer {
get { self[DIContainer.self] }
set { self[DIContainer.self] = newValue }
}
}
struct MainView: View {
#Environment(\.injected) private var injected: DIContainer
// `injected` has the `RealInteractor`, as expected
var body: some View {
SearchView()
}
}
// just tested here
struct SearchView: View {
#Environment(\.injected) private var injected: DIContainer
var body: some View {
Text("Result: \(injected.interactor.foo())") // << works fine !!
}
}
Here is an example:
struct ContentView: View {
var body: some View {
VStack {
SubView()
Button("Invoke"){
...// invoke subview's function
}
}
}
}
struct SubView: View{
var body: some View{
Text("SubView")
}
func changeSubView() {
//...
}
}
This is a simplified example. I don't want to use #State to control or pass function as a parameter.
Is there any other way?
I created a custom button, that shows a popover. Here is my code:
PopupPicker
struct PopupPicker: View {
#State var selectedRow: UUID?
#State private var showPopover = false
let elements: [PopupElement]
var body: some View {
Button((selectedRow != nil) ? (elements.first { $0.id == selectedRow! }!.text) : elements[0].text) {
self.showPopover = true
}
.popover(isPresented: self.$showPopover) {
PopupSelectionView(elements: self.elements, selectedRow: self.$selectedRow)
}
}
}
PopupSelectionView
struct PopupSelectionView: View {
var elements: [PopupElement]
#Binding var selectedRow: UUID?
var body: some View {
List {
ForEach(self.elements) { element in
PopupText(element: element, selectedRow: self.$selectedRow)
}
}
}
}
PopupText
struct PopupText: View {
var element: PopupElement
#Binding var selectedRow: UUID?
var body: some View {
Button(element.text) {
self.presentation.wrappedValue.dismiss()
self.selectedRow = self.element.id
}
}
}
That works fine, but can I create a custom event modifier, so that I can write:
PopupPicker(...)
.onSelection { popupElement in
...
}
I can't give you a full solution as I don't have all of your code and thus your methods to get the selected item anyhow, however I do know where to start.
As it turns out, declaring a function with the following syntax:
func `onSelection`'(arg:type) {
...
}
Creates the functionality of a .onSelection like so:
struct PopupPicker: View {
#Binding var selectedRow: PopupElement?
var body: some View {
...
}
func `onSelection`(task: (_ selectedRow: PopupElement) -> Void) -> some View {
print("on")
if self.selectedRow != nil {
task(selectedRow.self as! PopupElement)
return AnyView(self)
}
return AnyView(self)
}
}
You could theoretically use this in a view like so:
struct ContentView: View {
#State var popupEl:PopupElement?
var body: some View {
PopupPicker(selectedRow: $popupEl)
.onSelection { element in
print(element.name)
}
}
}
However I couldn't test it properly, please comment on your findings
Hope this could give you some insight in the workings of this, sorry if I couldn't give a full solution