I noticed a strange behaviour of XCode using swiftUI's withAnimation{}.
I created the following working example:
import SwiftUI
class Block: Hashable, Identifiable {
// Note: this needs to be a class rather than a struct
var id = UUID()
static func == (lhs: Block, rhs: Block) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
}
struct ContentView: View {
#State private var blocks: [Block]
init() {
var blocks = [Block]()
/// generate some blocks here
blocks.append(Block())
blocks.append(Block())
blocks.append(Block())
self._blocks = State(initialValue: blocks)
}
var body: some View {
VStack {
ForEach(blocks, id: \.self) { block in
BlockRow(block: block) { b in
print("COMMENT THIS LINE OUT") // try commenting out this line -> XCODE won't build anymore
withAnimation {
self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
}
}
}
}
}
struct BlockRow: View {
#State var block: Block
var onDelete: (Block) -> Void = {_ in}
var body: some View {
Text("<Block>")
.onTapGesture {
print("Tap block \(block.id)")
self.onDelete(block)
}
}
}
The above example works as expected, if you click on one of the blocks it get's deleted and the following blocks will nicly slide in the free position.
But XCode produces a warning here, I do not understand:
Result of call to 'withAnimation' is unused
Things get even more confusing: by just commenting out the print stamenet beforehand the withAnimation block /* print("COMMENT THIS LINE OUT") */ XCode will no longer build the project:
Failed to produce diagnostic for expression; please file a bug report
Is this a bug or am I completely off-road with my aproach?
The origin of the problem is the fact that self.blocks.remove(at:) returns a value that you are not handling. If you explicitly ignore that value, then your code works as expected whether or not the print statement is there.
withAnimation {
_ = self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
Swift has a feature that a closure with a single line of code returns the value of that statement as the return of the closure. So in this case, if you don't ignore the return value of remove(at:) it returns a Block and Block then becomes the returned type of the withAnimation closure.
withAnimation is defined like this:
func withAnimation<Result>(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result
It is a generic function that takes a closure. The return type of that closure determines the type of the generic placeholder which in turn determines the return type of withAnimation itself.
So, if you don't ignore the return type of remove(at:), the withAnimation<Block> function will return a Block.
If you ignore the return value of remove(at:), the statement becomes one that has no return, (that is, it returns () or Void). Thus, the withAnimation function become withAnimation<Void> and it returns Void.
Now, because the closure to BlockRow has only a single line of code when you delete the print statement, its return value is the return value of the single statement, which is now Void:
BlockRow(block: block) { b in
withAnimation {
_ = self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
}
and this matches the type that the closure to BlockRow is expecting for its onDelete closure.
In your original code, the print statement caused the closure to BlockRow to have 2 lines of code, thus avoiding Swift's feature of using the single line of code to determine the return type of the closure. You did get a warning that you weren't using the Block that was being returned from the withAnimation<Block> function. #Asperi's answer fixed that warning by assigning the Block returned by withAnimation<Block> to _. This has the same effect as my suggested solution, but it is handling the problem one level higher instead of at the source of the problem.
Why doesn't the compiler complain that you are ignoring the return value of remove(at:)?
remove(at:) is explicitly designed to allow you to discard the returned result which is why you don't get a warning that it returns a value that you aren't handling.
#discardableResult mutating func remove(at i: Self.Index) -> Self.Element
But as you see, this lead to the confusing result you encountered. You were using remove(at:) as if it returned Void, but it in fact was returning the Block that was removed from your array. This then lead to the whole chain of events that lead to your issue.
Use the following
var body: some View {
VStack {
ForEach(blocks, id: \.self) { block in
BlockRow(block: block) { b in
_ = withAnimation {
self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
}
}
}
}
Tested with Xcode 12.0 / iOS 14
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.
struct NavLinkLabel: View {
var text: String
var body: some View {
HStack {
Text(text)
Spacer()
Image(systemName: "chevron.right")
}
.padding()
.border(.bar)
}
}
func lbl(_ text: String) -> () -> some View {
return {
NavLinkLabel(text: text) //ERROR
}
}
This gives me an error on the marked line:
Cannot convert value of type 'NavLinkLabel' to closure result type 'some View'
This is despite the fact that NavLinkLabel conforms to the View protocol.
If I replace the offending line with NavLinkLabel(text: text).body, I get this:
Cannot convert value of type 'some View' (type of 'NavLinkLabel.body') to closure result type 'some View' (result of 'lbl')
Why can't it convert a conforming type, or an even an opaque type, to an opaque type of the same protocol? I don't doubt there's a better way to do this anyway, but I'm just experimenting and wondering why this doesn't work.
If you really need to return a closure (ie, actually a builder) then the simplest (and actually the one) variant is just to declare what is known concrete type (as we know what's returned), ie
func lbl(_ text: String) -> () -> NavLinkLabel { // << here !!
return {
NavLinkLabel(text: text)
}
}
Otherwise just return NavLinkLabel(text: text) directly, because the some support is limited in swift (at v5.6 time). It is only
'some' types are only implemented for the declared type of properties and subscripts and the return type of functions
means direct return type, not return of returned closure - it is out of scope.
You got an error because you wrap the returned View inside a closure{}. Also your return should be -> some View not -> () -> some View.
Try this instead:
func lbl(_ text: String) -> some View {
return DetailView(viewModel: vm) //this is my actual view and it worked
}
func customPrint(number: Int, via printingFunction: #escaping (Int) -> Void) {
printingFunction(number)
}
class Temp {
func tempPrintingFunction(number i: Int) {
print(i)
}
func print5() {
customPrint(number: 5) { [self] number in
tempPrintingFunction(number: number)
}
}
}
Temp().print5()
This code is fine: compiles and works as expected.
But when I pass function itself in capture list instead of self:
func print5() {
customPrint(number: 5) { [tempPrintingFunction] number in
tempPrintingFunction(number: number)
}
}
I get the error:
Extraneous argument label 'number:' in call
When I remove the label, everything backs to normal.
But it's weird. What is it? Bug or feature? I can't find any information about this neither in documentation nor on forums.
Bonus question:
Why doesn't the code crash, when I pass unowned self to the capture list? Shouldn't it crash? I want it to crash 🙂
Edited:
By the way, is there any difference in this situation between capturing self and function itself? In terms of retain cycles, etc. If we capture function, self will be captured as well, isn't it?
Let say I have two functions to create gestures:
func makeGesture1() -> some Gesture {
...
}
func makeGesture2() -> some Gesture {
...
}
and I want to use them like:
Text("abc")
.gesture( someCondition? makeGesture1() : makeGesture1().simultaneously(with: makeGesture2())
I got the error:
Result values in '? :' expression have mismatching types 'SimultaneousGesture<some Gesture, some Gesture>' and 'some Gesture'
If I wrap the expression with a function:
func makeGestureConditionally() -> some Gesture() {
if someCondition {
return makeGesture1()
} else {
return makeGesture1().simultaneously(with: makeGesture2())
}
}
I got the error:
Function declares an opaque return type, but the return statements in its body do not have matching underlying types
I found that I can do the following, but I wonder if there is a less hacky way, a proper way:
someCondition
? AnyGesture(makeGesture1().simultaneously(with: makeGesture2()).map { _ in () })
: AnyGesture(makeGesture1().map { _ in () })
When you use an "opaque return type" (like some Gesture), you are asserting to the compiler that you will only return 1 specific concrete Gesture type, you just don't want to write out its full signature.
This is why you cannot dynamically return different types at runtime.
This is why the ternary operator (?:) is failing as well; a ternary operator only accepts operands of the same type.
A workaround is use a SwiftUI ViewBuilder to let the SwiftUI runtime activate the correct gesture on the view based on the condition.
var baseView: some View {
Text("123")
}
#ViewBuilder
var content: some View {
if someCondition {
baseView
.gesture(makeGesture1())
} else {
baseView
.gesture(makeGesture1().simultaneously(with: makeGesture2()))
}
}
This is allowed as internally SwiftUI is using something called a "result builder" to only return 1 branch of the if statement at runtime, with each branch of the if statement having a concrete type.
In SwiftUI, avoid using any of the type erased wrappers (AnyView, AnyGesture etc.) as they increase the computational overhead on SwiftUI.
I am reading MirrorType at nshipster,and trying to adopt it's code to Swift 2.1. Everything works fine until when I tried to custom the _MirrorTypewith :
extension WWDCSession : _Reflectable {
func _getMirror() -> _MirrorType {
return WWDCSessionMirror(self)
}
}
An error occured :
error: Playground execution aborted: Execution was interrupted,
reason: EXC_BAD_ACCESS (code=2, address=0x7fff58273e87).
And I found out it's because the init method in WWDCSessionMirror was being called infinite times.
struct WWDCSessionMirror: _MirrorType {
private let _value: WWDCSession
init(_ value: WWDCSession) {
_value = value
}
var value: Any { return _value }
var valueType: Any.Type { return WWDCSession.self }
var objectIdentifier: ObjectIdentifier? { return nil }
var disposition: _MirrorDisposition { return .Struct }
var count: Int { return 4 }
subscript(index: Int) -> (String, _MirrorType) {
switch index {
case 0:
return ("number", _reflect(_value.number))
case 1:
return ("title", _reflect(_value.title))
case 2:
return ("track", _reflect(_value.track))
case 3:
return ("summary", _reflect(_value.summary))
default:
fatalError("Index out of range")
}
}
var summary: String {
return "WWDCSession \(_value.number) [\(_value.track.rawValue)]: \(_value.title)"
}
var quickLookObject: PlaygroundQuickLook? {
print(summary)
return .Text(summary)
}
}
I want to ask why it happened , and how to fix it?
_Reflectable and _MirrorType are not the droids you're looking for.
They are legacy types, which have been superseded by CustomReflectable (among others). The 2015 WWDC session about LLDB goes into some detail about this (disclaimer: I am the speaker of that part of that session, so conflict of interests all around :-)
But, anyway, the issue you're running into is because of this line:
_value = value
Since you're typing this line in your playground, that tells the playground logic to capture for display ("log" in playground parlance) the thing you're assigning. To do so, the playground uses the Mirror attached to that type. So, we go off and create one, which causes us to run
_value = value
again, which tells the playground logic to log value, which then means we create a Mirror, ...
You should first of all check if you can adopt Mirror and CustomReflectable instead of _MirrorType and if using those APIs fixes your problem. If it doesn't a possible workaround is to put the reflection support code in an auxiliary source file which will cause the playground logic to not log things inside of it.