A single component's animation is messing up the whole view — SwiftUI - ios

I'm building a SwiftUI app for my wife. Her avatar pulses with a repeating animation. It looks cool and seems to work just fine on its own. This is a component that lives in its own SwiftUI file.
When I bring a bunch of the components together to create a view, however, the animation messes it all up.
What a mess, right?
I assumed this happened because I did not define the width of the view. I used the fix from this issue, but it didn't make a difference.
I'm feeling pretty stuck here. Here's the repo for the project, with the code for this view. Does anyone have an idea for how I can fix this animation issue?

You need to remove the animation(nil) in the AvatarComponent
You also need to change all .frame(width: 352) to .frame(width: UIScreen.main.bounds.size.width)

The answer from E.Coms above was very helpful! But instead of replacing the width (325) of all my UI elements, I appended .frame(width: UIScreen.main.bounds.size.width) to the top-level wrapper. It seems to work just fine now!

Related

SwiftUI: Conditionally hide a view without recreating it

The issue with Conditional View Modifiers
I made heavy use of conditional view modifiers in SwiftUI until I had some issues with it and recently discovered it is a bad idea.
From my understanding now, doing something like this:
if condition {
view
} else {
view.hidden()
}
means that SwiftUI will treat view in both cases as completely different views. So if you jump from if to else or vice versa, view is recreated, including running onAppear and any initial animations. Besides that, it also breaks animating view from one branch into the other. Even adding .id() doesn't seem to solve this.
I attached a minimum viable example so you can see this behaviour for yourself. And needless to say, this is bad. It has major performance implications, breaks animations and is even worse if you perform some heavy operation (like a network request) in your onAppear.
The solution?
The solution appears to be to do something like this:
view.padding(condition? 10 : 0)
Everything works peachy, SwiftUI keeps view around, great.
The problem: .isHidden()
Now, I need to conditionally hide a view. And to be honest, I have no idea how to achieve it. .isHidden() has no parameter to enable or disable it, so there is no way to use the same approach as shown above.
So: How can I conditionally hide a view without recreating it? The reason I don't want to recreate the view is that onAppear gets called again and it reset the state of the view, triggering animations to the initial state again. Or am I using the wrong approach entirely in SwiftUI world here?
A possible approach is to use opacity+disabled, like
view
.opacity(isHidden ? 0 : 1)
.disabled(isHidden ? true : false)
In SwiftUI a View struct is just data, SwiftUI is what diffs the data and decides what actual UIViews to add, remove, update on screen. So if you want to hide something just give it empty data, e.g.
Text(showText ? "Some text" : "")
It would be helpful to know what data it is you are trying to show/hide.

SwiftUI Xcode 12.5 with very basic NavigationView layout Issues

I have been developing iOS for about a decade and every time I try to take the dive into SwiftUI I spend more time than ever wrestling what should seemingly be a simple task. While working on an app with very simple navigation setup I kept seeing two errors in the console: Unable to present. Please file a bug. and Unbalanced calls to begin/end appearance transitions for <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVVS_22_VariadicView_Children7ElementGVS_18StyleContextWriterVS_19SidebarStyleContext___: 0x7fd913d0bd90>. The first happens when there are at least 3 Views to navigate from (I don't understand why this is a significant threshold and my end goal uses a LazyVGrid with a ForEach) with navigation links and the second error happens on rotating to landscape and then back to portrait. I believed this to be related to how the phone is presenting the sidebar but even changing to StackNavigationViewStyle produced similar problems.
If this were a UIKit application I can absolutely solve for the Unbalanced calls situation but SwiftUI really takes away some of the lower level capabilities that I am used to having control of when it comes to building Views and navigation stacks.
I finally attempted to just start a new project from scratch and place the minimal amount of code in the ContentView:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Number 1")) {
Text("Number 1")
}
NavigationLink(destination: Text("Number 2")) {
Text("Number 2")
}
NavigationLink(destination: Text("Number 3")) {
Text("Number 3")
}
}
}
}
}
Running the above code produced the issues on rotation. Another issue that I came across was when you press the < back button after these issues start, the navigation stops working completely and the "detail" view never gets updated.
It is clear that the direction is SwiftUI so I am trying to really go this direction (maybe next week will have some amazing improvements) but this seems to be a pretty significant issue on a very simple set of code. I am hoping that I am just doing something wrong that someone can point out quickly.
EDITED:
After more exploration I found that the second error happens on the 11, 11 Pro Max, and 12 Pro Max where the navigation stack changes to the sidebar by default. Changing the style to StackNavigationViewStyle does eliminate the second error (but doesn't help if I do want to use the sidebar style) but the first error remains. To Schottky's point, changing VStack to List will also eliminate the first error. However a newer convention (based on WWDC videos at least) to solve for collection views I believe is to use a LazyV/HGrid with a ForEach within it which is actually what I am attempting to accomplish. I didn't put that as my code here since I wanted the be able to reproduce the error in the simplest form of course to ensure it wasn't something buried in my view hierarchy.
The way I was able to accomplish this was via a post I found at HackingWithSwift here. I still had to use .navigationViewStyle(StackNavigationViewStyle()) to eliminate a different error as it seems that the initial iterations of SwiftUI focuses on the 80% of cases where multiple NavigationLinks only really exist in the List View type. I have not tested this with the latest beta but if you try to use the other navigation style (Column) it will throw another error: unbalanced calls to begin/end appearance transactions. I am okay with this for now as this at least allows me to create a screen that does not have line separators and navigation arrows....I just don't have the Master-Detail setup by default.

UIView Doesn't Appear Immediately

I'm creating a UIView in one of my methods and going through all the steps of adding it to the view:
background1 = UIView(frame: CGRectInset(other.frame, -thickness, -thickness))
background1!.backgroundColor = aColor
background1!.hidden = false
container.addSubview(background1!)
This is included in a method I call in viewDidAppear(). In general, everything works perfectly. This view, however, appears noticeably later when opening the app the first time.
I can tell, from setting breakpoints before and after the above code, that background1 does not appear on the screen immediately after the code is executed, nor even in any place I've been able to set breakpoints.
I'm not doing anything else with the view—for some reason it takes surprisingly annoyingly long to load.
I have a very complicated (as shown by the >114 comments here) layout setup that's part AutoLayout and part programmatic and thus would like to just resolve this issue alone, if possible, without ripping up everything else.
I know that viewDidAppear() is, of course, called after the view appears, meaning the code executed should take effect then, but from what I can tell, background1 isn't even appearing at that point.
Additionally, as you can see in the first line of the code I posted, background1's frame relies upon that that of other, another subview. This view has its frame determined by AutoLayout. From what I understand, it's only guaranteed that the correct frame will be reported after viewDidLayoutSubviews()is called. (Incidentally, this is even called after viewDidLoad().)
I know I've already made a mistake by mixing AutoLayout and programmatic layout, but I'm hoping there's a way to salvage this. I do, in fact, remember, a time in a previous version when nearly the exact same setup was working perfectly. (Looking back through commits, though, I couldn't find it.)
To be clear, I guess I'm asking if there's a way to force the UIView to appear when the code is executed. I've tried calling the parent's layoutSubviews(), but that doesn't seem to work.
Thank you.

Flipped NSScrollView problems

I have an NSScrollView that needs to display a variable amount of NSViews in it. I made a custom NSView that has isFlipped return YES and put my NSViews in that before I set it to be the NSScrollView's documentView. That works well. It displays my items top to bottom like I would expect.
But, when the contents changes and I need to change the NSScrollView's documentView's frame, my contents disappears.
This would be WAY easier with a UIScrollView, but alas.
I'm assuming these are equivalent:
[NSScrollView.documentView setFrame:newFrame];
[UIScrollView setContentSize:newSize];
I would imagine that a lot of coders need to have a flipped NSScrollView, but how do you deal with changing the content size without this madness? Obviously, I'm missing something.
Anyone? Thanks!
can you try just setting
[scrollView.documentView setFrameSize:newFrame.size];
to see if your content disappears?
The problem was with the internal views and autoresizing/constraints. My internal views are actually a series of view controllers with their own xib file. In each view controller, I added the following line:
[self.view setAutoresizingMask:NSViewNotSizable];
And that solved everything.
Special thanks to lead_the_zeppelin for helping me (in chat) go through everything.

Custom Scroll view in cocos2d without using uikit

I currently need to create a custom scroll view without using UIKit's scrollview in cocos2d.
The best way, I think, is to create a separate layer and then add all my sprites to that layer. But I'm not sure how to receive touch events for all of the sprites. Is there a best way to do this? Thanks!
Have you seen CCScrollLayer? It might not be suitable for you but maybe you can copy the way that it is picking up touches.
https://github.com/cocos2d/cocos2d-iphone-extensions/tree/develop/Extensions/CCScrollLayer
http://www.cocos2d-iphone.org/forum/topic/17118
There's another one here as well, not sure if it's a fork or an independent one:
https://github.com/jerrodputman/CCKit
But I didn't have much success with any of these. The bounce and other parts of the experience never feel right, so I go back to using UIScrollView to handle the touches.
I've been facing the same issue and I found the SWScrollView here:
https://github.com/saim80/Cocos2D-Extensions
met my needs better than CCScrollView. It acts more like the UIScrollView where as CCScrollView is more for paging from what I've seen.
There is a nice framework called CMMSimpleFramework.
http://www.cocos2d-iphone.org/forum/topic/39018
http://www.cocos2d-iphone.org/forum/topic/60354
There are some sample videos, and the link to the repo is on those pages.
One of the classes is a scrolling layer that might do what you need.
To get the sample project to run, I had to comment out some game center authentication handler code that has changed, but after I did that the demo worked fine.

Resources