I have a custom view based on TheNeil's answer, that has two levels: MainContent and SubviewContent, both are Views.
The user can navigate from a single MainContent to many SubviewContent. This means I make the destination view's content dynamically.
The hierarchy looks like this:
struct StackNavigationView<MainContent, SubviewContent>: View where MainContent: View, SubviewContent: View {
subView: () -> SubviewContent
//...
}
struct SomeView: View {
var body: some View {
StackNavigationView(/*init parameters*/) {
//MainView
}
}
private func subView(forIndex index: Int) -> AnyView {
//some stuff...
}
}
SubviewContent is built from a method passed from the MainContent and stored as a variable like so:
let subView: () -> SubviewContent
During initialization, I pass an #escaping method and assign it to the variable subView.
init(#ViewBuilder subView: #escaping () -> SubviewContent, /*more stuff*/) {
self.subView = subView
//more stuff...
}
That method returns a view based on an index, like so:
private func subView(forIndex index: Int) -> AnyView {
switch index {
case 0: return AnyView(/*some content*/)
case 1: return AnyView(/*some content*/)
//etc...
default: return AnyView(ViewNotFound())
}
}
The problem is that if I pass a view that requires parameters, the parameters must be hardcoded. Otherwise, the parameters are nil and the app crashes.
Example case with parameter:
case 1: return AnyView( DestinationView(parameter: some_parameter) )
This crashes because some_parameter is nil, when the user tries to navigate.
The same issue appears with optional parameters. When unwrapped, they are nil.
I have tried removing the #escaping bit to solve this issue, but then the app crashes instantly because there are no parameters to pass (again, it passes nil but for a different reason, obviously).
How can I pass a fully constructed View only after the users select the navigation destination?
UPDATE 1
Upon review and discussion of the question, I believe a better way to clarify the problem would be this:
How is it possible to maintain in-method parameters while passing the method as a variable?
If you are just trying to pass imagepaths/text, maybe just make the subviews read it directly from somewhere else?
Class ParamsToPass {
static var data = [
"viewID" : [
["param1key": "param1value"]
["param2key": "param2value"]
]
]
}
On your update:
How is it possible to maintain in-method parameters while passing the method as a variable?
Maybe make the method return said parameters you need?
Related
In the example following, I'm calling an instance method of a View using the method directly (saveTitle), and via a pointer to that method (saveAction).
When calling the method, I am passing in the current value of the title variable.
When I call it directly, the current value matches the value inside the method.
When I call it via a pointer to the method, the value inside is the value it was when the struct was first instantiated.
It is almost like there is a new instance of the Struct being created, but without the init method being called a second time.
I suspect this has something to do with how SwiftUI handles #State modifiers, but I'm hoping someone out there will have a better understanding, and be able to enlighten me a bit.
Thanks much for your consideration :)
import SwiftUI
struct EditContentView: View {
#Binding var isPresented: Bool
#State var title: String
var saveAction: ((String) -> Void)?
var body: some View {
TextField("new title", text: $title)
Button("save") {
print("calling saveText")
// this call works as expected, the title inside the saveTitle method is the same as here
saveTitle(title)
print("now calling saveAction, a pointer to saveTitle")
// with this call the title inside the method is different than the passed in title here
// even though they are theoretically the same member of the same instance
saveAction!(title)
isPresented = false
}
}
init(isPresented: Binding<Bool>, title: String) {
print("this method only gets called once")
self._isPresented = isPresented
self._title = State(initialValue: title)
saveAction = saveTitle
}
func saveTitle(_ expected: String) {
if (expected == title) {
print("the title inside this method is the same as before the call")
}
else {
print("expected: \(expected), but got: \(title)")
}
}
}
struct ContentView: View {
#State var title = "Change me"
#State var showingEdit = false
var body: some View {
Text(title)
.onTapGesture { showingEdit.toggle() }
.sheet(isPresented: $showingEdit) {
EditContentView(isPresented: $showingEdit, title: title)
}
}
}
I don't think this is related to #State. It is just a natural consequence of structs having value semantics, i.e.
struct Foo {
init() {
print("This is run only once!")
}
var foo = 1
}
var x = Foo() // Prints: This is run only once!
let y = x // x and y are now two copies of the same *value*
x.foo = 2 // changing one of the copies doesn't affect the other
print(y.foo) // Prints: 1
Your example is essentially just a little more complicated version of the above. If you understand the above, then you can easily understand your SwiftUI case, We can actually simplify your example to one without all the SwiftUI distractions:
struct Foo {
var foo = 1
var checkFooAction: ((Int) -> Void)?
func run() {
checkFoo(expectedFoo: foo)
checkFooAction!(foo)
}
init() {
print("This is run only once!")
checkFooAction = checkFoo
}
func checkFoo(expectedFoo: Int) {
if expectedFoo == foo {
print("foo is expected")
} else {
print("expected: \(expectedFoo), actual: \(foo)")
}
}
}
var x = Foo()
x.foo = 2 // simulate changing the text in the text field
x.run()
/*
Output:
This is run only once!
foo is expected
expected: 2, actual: 1
*/
What happens is that when you do checkFooAction = checkFoo (or in your case, saveAction = saveTitle), the closure captures self. This is like the line let y = x in the first simple example.
Since this is value semantics, it captures a copy. Then the line x.foo = 2 (or in your case, the SwiftUI framework) changes the other copy that the closure didn't capture.
And finally, when you inspect what foo (or title) by calling the closure, you see the unchanged copy, analogous to inspecting y.foo in the first simple example.
If you change Foo to a class, which has reference semantics, you can see the behaviour change. Because this time, the reference to self is captured.
See also: Value and Reference Types
Now you might be wondering, why does saveAction = saveTitle capture self? Well, notice that saveTitle is an instance method, so it requires an instance of EditContentView to call, but in your function type, (String) -> Void, there is no EditContentView at all! This is why it "captures" (a copy of) the current value of self, and says "I'll just always use that".
You can make it not capture self by including EditContentView as one of the parameters:
// this doesn't actually need to be optional
var saveAction: (EditContentView, String) -> Void
assign it like this:
saveAction = { this, title in this.saveTitle(title) }
then provide self when calling it:
saveAction(self, title)
Now you won't get different copies of self flying around.
I have this function to return a reusable view when my side menu is clicked. When the function is called for the first time GameMenuView is initialized, but the next time it's called, the view is already initialized and I would like to reinitialize it since I want it to behave like a different view with onAppear being called and so on.
Any idea how to accomplish this?
func getGameMenuView(isGameOne: Bool, menuInteractor: MenuInteractor) -> some View {
let gameInteractor = GameInteractor(isGameOne: isGameOne)
return AnyView(
GameMenuView()
.environmentObject(menuInteractor)
.environmentObject(gameInteractor)
)
}
SwiftUI views are value types and provided function creates/initialized a new view, so I believe your issue is not in provided code but in code which uses this function.
Anyway try to give it unique ID (or in places where you use result of this function), this prevents SwiftUI equality optimization.
func getGameMenuView(isGameOne: Bool, menuInteractor: MenuInteractor) -> some View {
let gameInteractor = GameInteractor(isGameOne: isGameOne)
return AnyView(
GameMenuView().id(UUID()) // << here !!
.environmentObject(menuInteractor)
.environmentObject(gameInteractor)
)
}
I have a custom view which is going to be displayed in the collection view. View will be added in a stack view embedded in a view controller. A data source method is exposed to provide a view object.
Now here is the problem, before adding my custom view to stack I need to make a gateway call first to check if it is allowed to display the view or not.
As gateway call is asynchronous and returns me the response in the callback. Based on callback I need to instantiate the view and return in data source callback provided.
Here is demo code just for better understanding. Forgive me for my terrible naming conventions.
func customeViewInStack(customView: UIView) -> [UIView]? {
if viewIsAllowedToBeDisplayed {
self.downloadViewContent(onCompletionHandler: { contentDownloadSuccessfull in
if contentDownloadSuccessfull
// Return new instance of my custom view
else
// return nil
})
}
return nil
}
All the suggestions I read were about using a closure to pass the value, but due to data source method and callback in gateway call, I need to use old fashion return statement.
Edit 1
I have updated code snippet for better understanding.
I can not change function signature as it is part of a framework and a datasource method.
Also this method is shared between different features. What I mean by that is different views going to get added in stack view and they have their own conditional check wether to add them or not.
So basically what I am trying to achieve is until i do not get response from gateway operation, program execution should not proceed ahead.
I have tried using DispatchGroup class, but not able to achieve my goal.
Can someone suggest me on how should I tackle this problem
Thanks
About that, the correct solution is to not return something from the function, but instead give a completion to that function.
func customeViewInStack(customView: UIView, completion: #escaping ([UIView]?) -> Void) {
self.checkIfViewShouldBeShown(onCompletionHandler: { shouldDisplayView in
completion(shouldDisplayView ? [...(put the stack of views to return here))] : nil)
})
}
EDIT: I read it poorly first time, so I enhanced my answer.
As you need to pass it to collection view, I suggest that you will store it afterwards, and reload collection:
private var viewsForCollection: [UIView] = []
customeViewInStack(customView: UIView(), completion: { [weak self] views in
self?.viewsForCollection = views ?? [] // I guess optionality for array is unnecessary
self?.collectionView.reloadData()
})
At first, you will have an empty collection, but as soon as your views are ready, you can reload it.
You will need to declare your own completion block and use that to get your new view:
Here is what that declaration looks like:
// Declaration
func customeViewInStack(customView: UIView, completion: #escaping ([UIView]? -> ()) {
self.checkIfViewShouldBeShown(onCompletionHandler: { shouldDisplayView in
if shouldDisplayView
// Call the completion block with the views
completion(views)
else
// Call the completion block with nil
completion(nil)
})
}
And then in your usage of this, you would use it just like you used the checkIfViewShouldBeShown method:
customViewInStack(customView: customView, completion: { views in
guard let views = views else {
// Views are nil
}
// Do what you need to do with views
})
In Swift (I'm using 4.1), is there a way to do some clean-up in an extension when an object is being destructed? I have in mind the kind of code that would go in deinit(), but an extension can't declare deinit(). (Besides which, if several extensions needed to do this, there would be multiple deinit() declarations.)
I did not find a way to exactly get what you want, but maybe this code will help. I have never tried it, so maybe use it more as an inspiration. In a nutshell, it allows you to add bits of code that will excute on deinitialization.
/// This is a simple object whose job is to execute
/// some closure when it deinitializes
class DeinitializationObserver {
let execute: () -> ()
init(execute: #escaping () -> ()) {
self.execute = execute
}
deinit {
execute()
}
}
/// We're using objc associated objects to have this `DeinitializationObserver`
/// stored inside the protocol extension
private struct AssociatedKeys {
static var DeinitializationObserver = "DeinitializationObserver"
}
/// Protocol for any object that implements this logic
protocol ObservableDeinitialization: AnyObject {
func onDeinit(_ execute: #escaping () -> ())
}
extension ObservableDeinitialization {
/// This stores the `DeinitializationObserver`. It's fileprivate so you
/// cannot interfere with this outside. Also we're using a strong retain
/// which will ensure that the `DeinitializationObserver` is deinitialized
/// at the same time as your object.
fileprivate var deinitializationObserver: DeinitializationObserver {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DeinitializationObserver) as! DeinitializationObserver
}
set {
objc_setAssociatedObject(
self,
&AssociatedKeys.DeinitializationObserver,
newValue,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
/// This is what you call to add a block that should execute on `deinit`
func onDeinit(_ execute: #escaping () -> ()) {
deinitializationObserver = DeinitializationObserver(execute: execute)
}
}
How it works
Now let's assume you have your object, let's call it A and you want to create some code inside an extension, you can use onDeinit to add your clean-up code like this:
extension A {
func someSetup() {
// Do your thing...
onDeinit {
/// This will be executed when A is deinitialized
print("Execute this")
}
}
}
You can also add it from outside the extension,
let a = A()
a.onDeinit {
print("Deinitialized")
}
Discussion
This is different than what you want in that instead of definition a function (deinit) you need to call one. But anyway in protocol extensions, you can never really use the underlying object's life cycle, so it should be okay for most cases.
I am not sure about the order of execution between A's deinit and the DeinitializationObserver's deinit. You might need to drop the assumption that A is still in memory when you write the code inside the closure.
If you need more than one onDeinit you can update the associated object to retain an array of DeinitializationObserver
In my code, I usually use ReactiveCocoa's Lifetime for this kind of things. However, it is more complicated than what I wrote here. Instead, it looks like they swizzle the dealloc selector. If you're interested you can take a look inside - https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/NSObject+Lifetime.swift
I have my model implemented as structs in Swift 3.0. Several of these structs have delegates that should be able to modify the model depending on the user's actions.
However, when I pass the struct to the delegate method, it gets copied.
How do you solve this? Can you force the compiler to pass this struct as a reference, or the only option is to use a class?
structs are always passed by value. The whole point of using a struct is to have it behave as a value type. If you need delegation (which usually implies mutable state), you should be using a class.
If you really really need to, you could force pass-by-reference by using an inout parameter, but that is not recommended in general. You could also use a box type to simulate passing by reference. But, in general, you should just use a class if you need reference behavior.
The whole point of using struct in the first place is that this is desirable behavior. It preserves the immutability of the data. inout can achieve this, but it's not recommended in the general case.
protocol Delegate {
func callback(_ oldValue: Int) -> Int
}
struct IncrementerDelegate: Delegate {
let step: Int
func callback(_ oldValue: Int) -> Int {
return oldValue + step
}
}
struct Model {
var i = 0
}
class Controller {
var model = Model()
var delegate: Delegate
init(delegate: Delegate) {
self.delegate = delegate
}
// To be called externally, such as by a button
func doSomething() {
// Delegate determains new value, but it's still the
// controller's job to perform the mutation.
model.i = delegate.callback(model.i)
}
}
let delegate = IncrementerDelegate(step: 5)
let controller = Controller(delegate: delegate)
print(controller.model.i)
controller.doSomething() // simulate button press
print(controller.model.i)
protocol CrappyDelegate {
func callback(_ model: inout Model)
}
struct CrappyIncrementerDelegate: CrappyDelegate {
let step: Int
func callback(_ model: inout Model) {
model.i = 9999999
// Just hijacked the models value,
// and the controller can't do anything about it.
}
}
class VulnerableController {
var model = Model()
var delegate: CrappyDelegate
init(delegate: CrappyDelegate) {
self.delegate = delegate
}
// To be called externally, such as by a button
func doSomething() {
// Controller leaks entire model, and has no control over what happens to it
delegate.callback(&model)
}
}
let crappyDelegate = CrappyIncrementerDelegate(step: 5)
let vulnerableController = VulnerableController(delegate: crappyDelegate)
print(controller.model.i)
controller.doSomething() // simulate button press
print(controller.model.i) // model hijacked
If you want to pass by reference, you should generally use a class not a struct.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html states:
You can use both classes and structures to define custom data types to
use as the building blocks of your program’s code.
However, structure instances are always passed by value, and class
instances are always passed by reference. This means that they are
suited to different kinds of tasks. As you consider the data
constructs and functionality that you need for a project, decide
whether each data construct should be defined as a class or as a
structure.