Why ViewRenderer(s) inherit from UIView

Why do we need native views when ViewRenderer itself is UIView and first thing it does is place "native view" as it's subview on SetNativaCall(..).

I also see possible problems with this approach when huge (1000 x 1 000 000) views are used (inside scrollview) and CALayer might consume too much memory (inside native view it is possible to override Layer and return CATiledLayer).

Answers

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    I think it's one area where I guess we'll see improvements

  • adamkempadamkemp USInsider, Developer Group Leader mod

    ViewRenderer does a few useful things as a parent view. If you need to avoid the overhead you can instead implement the IVisualRenderer (or is it IViewRenderer? I'm not at my computer) interface and subclass whatever you want.

  • EricMaupinEricMaupin USXamarin Team Xamurai

    you can instead implement the IVisualRenderer (or is it IViewRenderer? I'm not at my computer) interface

    IVisualElementRenderer

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    The issue I see is that some of the Xamarin Forms controls like Image for example are rendered as two native views, a UIView with a UImageView as child.
    This creates a lot of native views.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    A UIView with nothing but a child doesn't actually have much overhead.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    That can't be a rule, it greatly depends on the UI.
    It adds up with other different things and as a result it hurts overall speed.
    Many Xamarin Forms controls work like this. But my hope is things will change in future, Xamarin Forms is still new.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    @ReinisLusis for a large number of views, the best thing is you implement some kind of UI virtualization. You do not create all controls in the scrollview, but only those which need to be visible.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Overhead is relative. Compared to an image a UIView with a single child and no other contents has trivial overhead. That means if you're concerned about the case of having a large number of image views then optimizing the overhead of that wrapper view isn't going to help you. The overhead of the image itself would be the primary concern so you would have to optimize it by reducing the number of image views.

    I would argue that any time you are concerned about having a large number of views on the screen the correct thing to do is to use virtualization to reduce the number of views rather than worrying about one extra wrapper view. Don't waste time optimizing the wrong thing.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    If the number of native views is double the number it should, this is not negligible.
    The wrong thing would actually be to waste time optimizing something just to as a workaround for different issue.
    Again, I hope team knows about these issues and will do something in future. Xamarin Forms is still new.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Do the math. If the overhead of a UIView is c and the overhead of an image is 100*c then the memory consumption of having n image views would be 101*c*n. Removing the extra view would only reduce that number to 100*c*n, which is slightly less than a 1% reduction in memory usage.

    The only way that it would be worth doing that work would be if the overhead of the wrapper view was close to the same as the overhead of the wrapped view. That is, if it were only 2*c then you would be talking about a 50% reduction in overhead by getting rid of the wrapper (going from 3*c*n to 2*c*c).

    My contention, and I challenge you to prove otherwise, is that the overhead of the image itself is several orders of magnitude larger than the overhead of the wrapper view, which means it is a negligible concern. In other words, you're not going to go from "my app doesn't fit in the available memory" to "my app does fit in available memory" by removing the wrapper.

    Taking this further, I believe the overhead of any view with actual rendered content meets the same criteria. The vast majority of the memory taken up by views is in the contents of their backing layers, and a view that doesn't do any rendering and only has child views doesn't have any contents in its backing layer, and thus consumes very little memory compared to views that do render something.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    The original OP question is why does it need two native views for one Xamarin Forms control. Not how much overhead it generates.
    I can understand for some controls this might be necessary, but not for all.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    And I explained why, and also explained why he shouldn't worry about it. I don't know why you're trying to contradict that. Of all of the things the Xamarin.Forms team needs to focus on, this really isn't one of them.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    Easy, are you part of the Xamarin Forms team with a deciding role?
    It's only the team who decides what's next they should focus on.
    Also, I like to think everyone's entitled to have his\her own opinion.

    I might have missed it, but I don't see where you explained clearly why it requires two native views instead of one.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    I explained it here:

    ViewRenderer does a few useful things as a parent view.

    It's a code reuse thing. Using IVisualElementRenderer directly requires reimplementing things that are taken care of for you when you inherit from ViewRenderer.

    My point is that the fact that using ViewRenderer adds another layer doesn't actually hurt anything so don't worry about it. You're entitled to your own opinion, but not your own facts. The fact is that the overhead is trivial. It's the wrong thing to optimize.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    I already somewhat know that reusing is the reason but I haven't yet checked or thought more about it. That's the whole point of this thread.
    For some controls, there is just one native view created.
    Some controls like Image render as two native views, and for this specific control it doesn't make sense, but again, it might make sense, not sure, because I haven't spent too much time on this.
    It's not cool nor professional you're tying to school people around here. I've seen you doing this few times. You can say your opinion without the aggressive note. Definitely here's not a good place to do this. We're all here to share and help each other. I think.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    I'm only responding this way because I feel like you're misleading people, and I think it's important to explain why the thing you were saying is wrong. Again, this is not a matter of opinion. It's a fact that the overhead of views is trivial compared to the overhead of images. When you try to contradict that as you did here and here then there's a risk that people come away with the impression that they should try to do unnecessary work to reduce that overhead. I explained why that claim was wrong, but you kept coming back and implying that this was still a concern. It's no defense to claim that this is an opinion because it's really not. The claim that this overhead is significant is either true or false, and unless you can come up with a valid argument or evidence that it is actually true then you should really just drop it instead of continuing to argue your point. That's not helping anyone. So in the future unless you actually think I'm factually wrong and can back it up (and I do admit when I'm shown that I'm wrong) then don't waste time and confuse people by continuing to argue because I will continue to explain why you're wrong so that people don't get misled by false statements.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    Wow. I will leave aside your allegations. Not cool.
    I can't believe you're doing this on the public Xamarin forum.

    You're confusing it badly.
    First, the OP of this thread clearly said that his scenario is

    when huge (1000 x 1 000 000) views are used (inside scrollview)

    My opinion was that UI virtualization is the way to handle this, and it has nothing to do with Xamarin or renderers or the way Xamarin Forms control render to native views.

    Second, the OP was asking why the Xamarin Forms controls he observed render as two native iOS views. It's unrelated with the the first thing. I don't know if the OP made a connection between these two things.

    I didn't suggest nor said that the overhead is significant. I didn't suggest any solution. You're confusing things.
    All I did was I said I have the same question as the OP. Already mentioned few times that there might be good reasons why XF team chose this solution, it's just that I haven't yet found myself the answer. I'm referring to the Image control for example.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    I answered this question (the very first sentence in the post):

    Why do we need native views when ViewRenderer itself is UIView and first thing it does is place "native view" as it's subview on SetNativaCall(..).

    My answer explained both why that is the case and why it's not an issue to be concerned about.

    My opinion was that UI virtualization is the way to handle this

    I totally agree, and I said so.

    I didn't suggest nor said that the overhead is significant.

    You did say that it was an "issue", and that it "adds up with other different things and as a result it hurts overall speed". It's not an issue, and it doesn't hurt overall speed. So that's what I was responding to. I don't know why you took offense to that. I'm not going to keep having this meta-argument. I don't see what the point is anymore.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    You did say that it was an "issue", and that it "adds up with other different things and as a result it hurts overall speed". It's not an issue, and it doesn't hurt overall speed.

    Yes, it's an "issue" creating double the native views IF this is not really necessary.
    It's common sense to think it's not an ideal behavior. How big or small this issue is, it depends.
    For example, maybe someone creating X views on a Page, even if X is large, it could be fine in terms of speed. But if the X views renders as 2 * X views this might start not look OK. And person starts thinking for a workaround and ends up spending time on something which otherwise shouldn't be.

    Maybe stop jumping into conclusions so fast, stop attacking and making false allegations on other members here would be a good start.
    If you got something to say, share, agree or disagree you can do that without schooling people or make allegations. Not cool. Not professional.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Yes, it's an "issue" creating double the native views IF this is not really necessary.

    The fallacy in this statement is the assumption that double the views means double the memory, which is simply not true. When the subview is 100x the size of the wrapper view then having the wrapper view adds an insignificant amount of overhead. It's like saying "it's an issue to have one extra 32-bit variable in an class that also contains a 1kB array". You're not going to do any good removing that 32-bit field.

    A view that renders anything on screen (including image views, but also every other view that draws stuff) is going to have several orders of magnitude more memory overhead than a view that doesn't do any rendering itself. Therefore having twice the number of views is not an issue because those wrapper views contribute a negligible amount of memory relative to the views they wrap.

    On a side note, I think marking my comments as abuse is petty and unwarranted. We're having a technical discussion, and I said you were wrong (with detailed explanations about why). I haven't abused you or insulted you. Calm down.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    The fallacy in this statement is the assumption that double the views means double the memory, which is simply not true.

    You can't just stop making assumptions on other people.
    Why do you need to turn discussions into personal arguments. This is my last post.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Ok, here's an assumption: I think there must be a cultural disconnect and possibly a language barrier here. What you just quoted was not a personal argument. "Fallacy" is a term used to describe a logical flaw. That's it. What I said was "your statement had an incorrect assumption", and then I went on to explain what that incorrect assumption was. That's not personal! I think what's happening here is a cultural difference. I'm an American, and our culture is to be direct and to the point. When I say that something you said is wrong that's not meant as an insult. I am truly sorry if what I have said offended you, but I can say with complete honesty that nothing I have said here was in any way intended as an insult. I am only responding to and attempting to correct factual claims that you are making. Please don't assume that I am insulting you just because I am contradicting you.

  • TektonTekton USMember ✭✭✭
    edited June 2015

    From an outsider's perspective, I could see how this all turned out like this. However, it's small beans. From reading a lot of posts, in the background, I have to say you both have my respect. (In the voice of Forrest Gump, "And that's all I have to say about that.")

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    I think you guys have gotten the wrong idea from each other. @AndreiNitescu I don't think @adamkemp was being argumentative, I'm British, from a greek mother and I'm multi-lingual, as are my wife and son, so I'm used to lost-in-translation moments : I think he actually went to great lengths to try to help you see things clearer, and it seems you had some kind of miscommunication between you. It'd be a shame for you to have hard feelings over it as it's nothing, and Adam is about as passive as they come (he's told me calmly a few times when I was throwing my toys out of the pram, which I'm prone to do once in a while).

    However, this is something I scratch my head about sometimes too; but Adam is right - this has zero implications for performance, which is an area of iOS that I'm extremely well versed in. There are all kinds of optimizations which occur in the underpinning CGLayers, which is why iOS dev's no longer had to tear down views since a few versions back of the OS.

    I personally find these extra views ugly, as I used the marvellous reveal app for debugging my UIs; but if these views were causing even the remotest impact on your apps performance, there'd be something severely wrong with your app (like it went in a loop and spat out several hundred of them for fun or something).

  • ReinisLusisReinisLusis LVMember

    The reason why I started this question is because I am developing application that displays images in scrollview (grid of images). I created control that does virtualization and stacking of images (like WPF ItemsControl). This control has massive size and it creates child elements (Images) and layouts them in grid. It only creates ~15 child elements and rearanges them on scroll event so that there is no memory overhead. However - performance is very poor on older devices (iPhone 4). Performance was poor even when I switched to drawing colored bricks (Grid backgroundcolor) instead of images. Also after some scrolling app would crash on device because of outofmemory exception.

    I just want to confirm few things that I "heard" in this topic:
    1) UIView size does not affect memory consumption (so there is no need to worry about CALayer or CATiledLayer or whatever)
    2) Stacking UIViews that do not draw anything have no performance impact at all (and no memory impact regardless of view size or its property IsOpaque)
    3) Image is bottleneck here

    I wonder why it is still slowish when I draw colored brick instead of images?! What more optimizations can I do? Can I freeze layout before rearranging UIViews? Or are there some other tricks?

    Thnx!

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    It sounds to me like you have a memory leak on your images.

    Can you share code? And why are not using a grid view for this?

    if your ui view just contains things then it has no bitmap which means no overhead. If you set a colour on it then it will have a bitmap. This means it will take up ram relative to its size. That answers 1 and 2.

    I think image is the issue. But if you have huge views with background colours hats going to be an issue.

    But why not xlabs grid view? Why roll you own?

  • ReinisLusisReinisLusis LVMember
    edited June 2015

    GeorgeCook - at which point/how does UIView decide that it needs a backing bitmap relative to its size? DrawRect gets called always (i guess).

    I am not using XLabs GridView because:
    1) I started developing my own ItemsControl and only then learned about XLabs, but
    2) when I learned about XLabs (by reference to RepeaterView) I found that XLabs has some poor code (although it had DataTemplate of which I was happily surprised)
    3) so I downloaded it but had problems compiling it and decided not to waste more time on it (however I used some of code from there as reference (carousel view was useful))
    4) repeater view did not seem to support virtualization and I did not figure it out that GridView kind-off does what I need (I will look at it's code carefully to understand best patterns)

    My opinion is that it is not bad spent time to re-develop already existing component (especially if it is complex one) because by doing so one learns fundamentals of new environment and shapes deeper understanding of abstraction levels used by given software library.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    @ReinisLusis There can be few reasons why you get out of memory exception.
    Stick to the colored boxes for now and try to understand what's the issue (I would use a BoxView instead of Grid for testing, because the former technically is simpler, but it shouldn't matter).

    It only creates ~15 child elements and rearanges them on scroll event so that there is no memory overhead. However - performance is very poor on older devices (iPhone 4). Performance was poor even when I switched to drawing colored bricks (Grid backgroundcolor) instead of images. Also after some scrolling app would crash on device because of outofmemory exception.

    15 child elements is extremely little and it should work great.

    2) Stacking UIViews that do not draw anything have no performance impact at all (and no memory impact regardless of view size or its property IsOpaque)

    If you are changing IsOpaque as you scroll this is a big issue because it invalidates the whole layout each time you change IsOpaque for each of the child views.
    Also, you shouldn't have all the child views created. That's the point of UI virtualization.
    For example, if when you scroll you can only see 3 items at once out of 15, than you must have only 3 child views created all the time. If the size of the child view is different and sometimes you have 3 views visible, sometimes 4, then the number of instantiated views becomes 4. As you scroll these views are reused. It's like how native ListView on platforms work.
    There are advanced scenarios where you have a zoom in \ zoom out feature and the number of instantiated views becomes really huge, and you need to overcome that, but not sure if it's your case.

    Would be much easier if you share your code.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    @GeorgeCook If you read all the posts, I never said that the issue OP has with performance is because sometimes a XF control renders as several native views. OP mentioned about this behavior and so did I.
    I never said OP should investigate this as the source of his problems.
    I'm done on this 'matter'. Always sad to see people feel like they have to show their muscles when it's not even the case.

  • ReinisLusisReinisLusis LVMember

    Hi! Here I am attaching source. I made simple test application with just grid. I have verified that virtualizing panel no longer create new controls so all what is happening when scrolling is UIView bound is changed and BindingContext is changed. Yet you will notice that scrolling is not smooth on iPhone 4.

    Test.zip 170.6K
  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    Just as a quick feedback, I wonder if you you put some Debug.WriteLine within the RefreshMeasurement method, somewhere where you can see how many computations are done when scrolling.

    Have you tried to debug where the bottleneck could be?

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited June 2015

    I still don't know what you're tying to do exactly.
    I see you have a custom control ItemsControl which you put withing a ScrollView.
    What's the logic by which you layout the views within the ItemsControl?

    For example I see you're deriving from View and not Layout for example.

  • ReinisLusisReinisLusis LVMember
    edited June 2015

    There was a reason why I did not derive from Layout. Layouts usually have"Content" property where children are placed from XAML. On the other hand ItemsControl creates children using DataTemplate. WPF's ItemsControl has parameter where panel can be specified. This way WPF's ItemsControl would create panel of that type and append children under it. But going this route would make it hard to introduce Virtualization so I decided to implement layout of items as part of ItemsControl.

    ItemsControl has these relevant parameters-

    • ItemsSource
    • ItemTemplate
    • ItemWidth
    • ItemHeight
    • VirtualizationFrame <- updated on ScrollView scrolled event

    ItemsControl calculates-

    • ItemsPerRow = Width / ItemWidth
    • HeightRequest = ItemsSource.Count / ItemsPerRow * ItemHeight

    it then will find all items from ItemsSource which are would be inside VirtualizationFrame. It will then create Views (using DataTemplate) or pull them from cache (for reusing) and assign their BindingContext = ItemsSource[index]. All visible Views (inside VirtualizationFrame) would be assigned to ObservableCollection Children property.

    ItemsControlRenderer would then create UIViews from ItemsControl.Children using RendererFactory or pull them from cache (reuse). ItemsControlRendered will then update bounds of UIViews.

    I have tried profiling it using "Instrumentals" but it showed no usable information.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    @ReinisLusis this works smoothly on my iPad from 2012 and I see you put 10,000 items

  • adamkempadamkemp USInsider, Developer Group Leader mod

    1) UIView size does not affect memory consumption (so there is no need to worry about CALayer or CATiledLayer or whatever)

    The Frame/Bounds of a view don't affect its memory consumption per se. What matters is the size of its layer's contents. A view that doesn't draw anything doesn't have any layer contents so the size of the view has no effect at all on its memory consumption. A view that draws itself will occupy enough memory to hold backing layer (multiple copies, potentially), and the size of the backing layer (in pixels) is the size of the bounds multiplied by the ContentScaleFactor.

    2) Stacking UIViews that do not draw anything have no performance impact at all (and no memory impact regardless of view size or its property IsOpaque)

    The Opaque property has no effect for views that don't draw anything.

    at which point/how does UIView decide that it needs a backing bitmap relative to its size? DrawRect gets called always (i guess).

    The UIKit framework is smart enough to know whether you have implemented Draw (i.e., it inspects the class to see if you have overridden that method), and if you have not implemented Draw then it doesn't allocate the backing layer, and it doesn't call the method. That's why sometimes you see templates for UIView subclasses that have Draw commented out with an explanation that if you don't need to draw then you should not include that method. It's not a good idea to have an empty implementation of Draw because that causes the system to create a backing layer for no reason.

    I believe, but have not proven, that setting a background color on a view does not cause it to have a backing layer (at least not one which takes up significant space). That is, setting a background color takes less memory than overriding Draw and explicitly drawing a fill color. Therefore even having a view just to have a background color shouldn't cause significant memory overhead.

    I took a look at your code, and I've noticed that you are doing a lot of work on every scroll event. If there's a performance issue with scrolling then it is most likely caused by how much work you're doing. For instance, you are allocating several collections (observable, no less) and adding things to them. Allocations during scrolling events should be kept to an absolute minimum. That puts a lot of pressure on the GC, and when the GC runs your application will temporarily pause. We had to do a lot of optimizations in our last application to avoid doing allocations during interactive gestures, and I wouldn't be surprised if that is also causing your performance issues.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    I tested it on an iPhone 4s, it's smooth. One thing I noticed the scroll thumb doesn't move though as you swipe.

Sign In or Register to comment.