I'm quite new to iOS drawing and I have some difficulties to see the different strategies (only one view that draws everything, a view that use other subviews, ...).
I would like to draw a graph like this :
I'm confused about how to draw this :
1st solution
1) draw what I call a "unit bar" (for example the first red bar below) in a UnitBarView file.
2) creating a second view "FourUnitBarsView" which would be an addition of 4 UnitBarView.
3) drawing the graph in a new view using "FourUnitBarsView"
2nd solution
1) drawing a unit of four bars "FourUnitBarsView" passing an array of colors and an array of values.
3) drawing the graph in a new view using as many subviews ("FourUnitBarsView") as necessary
3rd solution
using just one view and drawing everything in it
Unless your one huge view is able to do partial redrawing, then separate subviews should be more efficient, as the system can optimise its drawing to only the parts that need to be redrawn. However, I don't know if having substantial amounts of views could cause performance overhead in other ways, for example hit-testing and the likes.
All in all, I would suggest implementing whatever is fastest for you to build, and if it turns out the performance is blocking the application, you can try out alternative approaches, otherwise it sounds like premature optimisation.
Related
my question can be a little bit confusing, however I will do my best to explain it. So I need to create a view like below in the image, which looks like a line chart.
In other words, the line thumbs should be interactive, so that user can move them up and down, which will led to the movement of "ropes" between each pair. The problem is I can not figure out to start what kind of structure in swift so that I can create this view. So far, I am thinking about to create separate sliders and connect them by drawing, lines between them, but I think there should be some better solution. Any help, idea, advice or hint is appreciated. Thanks in advance.
Part 1. Learning to use "drawRect" in a UIView
Make a custom view, i.e. subclass UIView. To draw the nine angled line segments (and indeed the grid behind) you'll need to master core graphics. Fortunately there are many of QA on this very topic:
How to draw a line in the simplest way in swift
Part 2. Custom layers in UIView
You'll have to learn about adding custom CALayers to views. There are many examples of this, eg
https://stackoverflow.com/a/57465440/294884
https://stackoverflow.com/a/41553784/294884
(Note that for the small text labels, I would probably simply add many UILabels programmatically, which you will also need to learn about.)
Part 3. Using UISlider
There's really nothing wrong with using a UISlider for each of your red dots. If you're just getting started with iOS, I suggest trying that first to become familiar with it.
A handy tip is, simply use a horizontal stack view to hold them all - you can space them as you wish.
Part 4. Using gestures
Beyond UISlider. The red buttons would most likely be custom UIViews. And, most simply, you would use UIPanGestureRecognizer to detect the finger moving.
Again you can find many QA on this field of study, example Move UIView within parent view using pangesture
If you master these four general fields you will be able to achieve the view in question, good luck!
Using UICollectionView and excellent help given on here on StackOverflow, I've been able to build a "Gantt Chart" style control for my iPhone:
Cosmetics aside (I'm doing the functional right now, I've got a graphics designer on tap to look at colors and all that).
Background aside, the spans were relatively straightforward to do with a custom UICollectionViewLayout subclass. Each span is an item.
But I need to add some functionality, and am unsure how to proceed. Where I'm trying to go is illustrated roughly as:
Sketchy cosmetics aside, the point is that I want to "annotate" whatever the currently selected span is with additional information (I promise to find someone to help me look it pretty). And I want them to be active, I'm not sure if it brings up an editing control or does drag, but I want to be able recognize gestures on either the numbers or the bold lines and do things with them, distinct from touching on the span which drives selection.
I can think of (at least) 3 ways to try and implement this:
Use supplementary views. Cause selection to invalidateLayout, detect the selected state in my prepareLayout, and generate additional layout attributes for the two anchors. Implement a subclass of UICollectionReusableView which does the drawing, and adds touchable subviews (or its own gesture recognizers). This feels... wrong. I get the idea that supplementary views are more for headers and footers, not for controls that come and go as the selection state changes. But maybe it's an appropriate extension of the facility?
Use the backgroundView (or selectedBackgroundView, not sure it matters) of my current SpanCell class (which is a subclass of UICollectionViewCell). As long as I disable clipsToBounds, I can draw the annotation around the bounds of the span. I'll have to give it some knowledge of the big picture to find the endpoints, but that's not too offensive. I would just show/hide this view in response to selection changes. This seems like the best way to do it.
Do it in the main backgroundView of the entire UICollectionView. As shown, I've already got a specialized backgroundView which shows the the current time grid, strip style. I could further extend this view to draw annotations and manage touchable sub controls in response to selection changes. This would give me most direct implementation, but it feels like I'll end up with a big monster "doing too many jobs" object for the background.
Question then, for those who have more experience, is which route would you go? Would it be one of the above 3? Or something different? And why?
While your question is very technical with UICollectionView implementation, which I am not very familiar with, this seems like a job for the container (in this case, the collection view). Imagine you need your annotation to consider, in addition to the selected item, other items? Like for example, avoiding collision between annotation lines and another item?
For me, option number 3 seems like the most correct one. If you fear a large class, you can extern it to an annotation controller class, which should be notified whenever the annotations should be updated.
EDIT:
Imagine that I'm in the business of reimplementing CoreText.
I'd get an NSString here which is an ordered list of code-points, and a set of glyphs there (an NSFont/CTFont) that basically is a list of CGPathRef.
Something in-between is in charge of the layout.
And some kind of environment must provide a CGGraphicContext to render into.
The kind of objects I have to display are CGPath. I am worried of creating one UIView for each CGPath! For a block of text, you wouldn't create an UIView for each character-glyph, would you?
I have a potentially large set of (tiny) objects to draw at once. In the 1000's. Many will be offscreen until scrolled into view.
One one hand it looks like my best bet would be to create one UIView for each one. The reasons are that:
with proper tiling I can make sure that the views that are offscreen
are not even in memory most of the time and brought up to life when
required
more importantly, when zooming I want to set each such view's layer contentScale to the zoomFactor such that I still get the full
precision and not the blurry stair-case
On the other hand I have no idea how the (otherwise perfectly generic) parent view will behave with 1000's of children.
So I am tempted to rethink my code such that the "children" just become an area of the single view.
But then I start thinking of setting that view's layer contentScale and I'm worried about exhausting memory just while zooming: I do not know if the layer machinery is "clever" enough not to create a huge backing bitmap when most of its contents would be invisible anyway
What to do, oh what to do?
Anyone has any experience they want to share?
I recently saw a presentation on a game involving hundreds of sprites implemented as UIViews, mostly image views. Performance was excellent on both an older and recent generation iPad. His code is available at https://github.com/bentford/ButtonWars/ , so you may want to test it to see if the performance meets your needs.
There is often an assumption that views are heavyweight, but experience doesn't bear that out.
If most of the objects will not be on the screen at any time, it's probably better not to create views for all of those off-screen objects. You need to intelligently add views for just those objects that are visible, and remove them from the view hierarchy when they go off-screen. You might be able to use UICollectionView to make this easy - it is very well designed, efficient, flexible, and easy to use and customize - but I don't know if it works with zooming.
If you decide to draw everything in one view, with deep zooming, you should look at CATiledLayer. It is designed for zooming in very deeply. Apple's MKMapView uses it under the covers to support scaling its viewport from Earth-sized down to a (relatively) few square meters.
If you cant use UICollectionView due to iOS version boundations, and you are intrested in re using views in UIScrollView, than have a look at this class. It is used the same way UITableview is used. Just pass in number of views and it will reuse the views.You can go through readme. Its very straightforward.
I'm looking to build a document "selector" screen, similar to Pages and Numbers on the iPad/iPhone:
I come from a WPF background and have some iOS experience. That being said, I'm looking for a good approach to building something like the tile-based interface Apple uses for opening documents in Pages.
I'm not concerned with the Folder animation.
What is the best way to approach building just the tile interface? I'd imagine I'd build some sort of view that sits within a UIScrollView - but it's the nature of that subview that I'm a little confused about. Does iOS have any wrap-panel or grid-like controls I could load a set of tiles (i.e., documents) into?
What do you guys think?
I don't know of any third-party classes to handle this for you, but there might be some out there.
The basic structure will be a UIScrollView containing a set of views, each representing one cell on the grid. You set the scroll view's contentSize based on the total number of tiles. Then you create tile views on demand, and place them inside the scroll view.
The scroll view's delegate object will be responsible for monitoring the scroll position, putting down tile views as they become visible, and (optionally) removing tile views that move out of view. This is basically how UITableView works: there are only about six or so instances of UITableViewCell at any given time, they are recycled as you scroll up and down the view. (Imagine a train where somebody at the back end is pulling out the rails and passing them forward to somebody in the front, putting them down in front of the train. As far as the train knows, the rails go on for miles.)
If you wind up having to place all the views yourself, take some time to learn the CGRect family of methods, including CGRectDivide. They will be useful in laying out the views and also in computing what's visible and what's not.
There are a few third party classes/libraries you can get to produce this functionality, AQGridView comes to mind. But there are no default easy classes for this.
If I was to develop this type of implementation, I would subclass UITableViewController. Expand it to have columns. Then subclass a UITableViewCell to display the image. That way all the container code and everything would already be there, and all you have to do is customize it to f it your needs.
I have an app where I want to have one “background” view (view controller view), and on top of that, multiple UIViews that draw themselves as circles. I just don't understand how to implement all of this while still adhering to MVC...
The model and the view should be different. Does this mean that I generally need one set of header and implementation files for the view and another (separate) set for the model — even if my object is just a Circle class? Or, would I have one Circle.h and Circle.m set, and then inside the header file have two interface blocks, one for the model and one for the view, and two implementation blocks (again, one for the model and one for the view)?
I’ve seen a few people recommend using the view controller to handle all of the views in loadView, and to avoid creating separate custom view objects; does this mean that I shouldn’t create a separate set of .h and .m files for the view? The latter seems a lot more organized in terms of design to me.
Also, if I were to have multiple view objects and multiple model objects handled by the view controller, I would store these in two arrays in the controller — one for models, and another for views. Right? My question is: When I use key-value observing on each model, how would I, for each model object, alter the correct corresponding view object (assuming that indexes are not the same for both objects in both arrays)?
I was thinking about using a pointer to the model object inside of the view, and KVOing from the view to the model — but then it doesn’t go through the controller and thus breaks MVC altogether, right?
Thank you in advance.
MVC is a big, broad idea. It's more a guiding philosophy than a specific rule, and it's not always implemented the same way. Read Apple's discussion of MVC to appreciate the difference between traditional MVC and MVC in Cocoa.
It's hard to say how to apply MVC to your app because you haven't told us what the app should do, and also it doesn't sound like a realistic application. So I'll do my best here and make some assumptions along the way. An app that just draws a bunch of circles in fixed locations on a background isn't very interesting -- it could be almost all views, barely any need for a controller at all. So lets say that the circles are all moving in different directions, are drawn in different colors, and change in size over time. Now you start to have a need for a model, so that you can keep track of the data that these circles represent, and a controller to translate the model into terms that can be represented by the views.
Since you asked specifically about drawing circles, lets start with the view. It seems like a good idea to have a custom view that knows how to draw a circle given the necessary parameters: area, color, and position. You'd probably make these things properties, and override -drawRect: such that it draws a circle of the given area in the given color.
We don't know what these circles represent, but it's not much fun if they don't represent something, so let's postulate that the app's job is to help us compare corporations. We have data on revenue, market capitalization, number of employees, credit rating, name, ticker, etc. You could create a custom object to store all the data for each corporation, or you could put it all in a dictionary. Our model is a set of these custom objects or dictionaries.
Notice that the circle views don't know anything about corporations, and the model doesn't know anything about circles. This is A Good Thing. It's also where the controller comes in. The controller is where you put the code that expresses the model visually using the views. It also interprets events from the views, updating the model as necessary. So, our controller knows both about the particulars of the corporations, and the properties of the circle view. It creates a circle view for each corporation in the model. I want the area of a circle to correspond to the corporation's market cap, the vertical position to represent the revenue, and the horizontal position to indicate number of employees. We'll assign a color based on the corporation's credit rating. The controller should, of course, keep track of all the circle views and some way to map between circle views and corporations.
Now you've got something. It's still a pretty simple application, but you've got a useful chart comparing corporations in several dimensions. Let's improve it.
First, it's hard to know which circle represents which corporation. It would be nice if a circle view could optionally display some text. Let's add title and subtitle properties, and modify -drawRect: to draw these strings above and below the circle, respectively. We'll also change the controller so that a tap or click on a circle sets the title and subtitle of that circle to it's corporation's name and ticker symbol, or clears them if they were previously set.
Second, it's nice to compare corporations at a moment in time, but more interesting if we can show changes over time. Let's change the model to include historical data for revenue, market cap, employees, and rating. We can update the controller so that it can uses the historical data to animate the circles.
The first change related to how we draw information on the screen, and didn't require any modification to the model at all. The second change was about what data we have to work with, and didn't require any change to the view at all. You could easily re-use the circle view to represent some other kind of data, or maybe even to be the puck in an air hockey game. It's just a colored circle. And you could re-use the model in another app that deals with the same kind of data.
I'm sure the hypothetical application in this very long-winded explanation-by-example bears roughly zero resemblance to your own application, but perhaps it'll help to explain why MVC is useful and inform the structure of your own application. Good luck.
I got good explanation from CS193P IPHONE APPLICATION DEVELOPMENT (Winter 2013).
Model-View-controller:
Divide all objects into three camps:
Model : What your application is (A Card , A Deck , card game logic)
Controller : How your model is presented to user (UI Logic). Controller knows everything about the UI.
View : Your controller's minions.
How those camp communicate :
Don't cross yellow line.
You can cross over Dashed white line.
some special rules are there for crossing solid white line.
Controller --> View (via Outlet)
View --> Controller (via Data source (count, dataAt), delegate(will, should, did), Target-action)
Model --> Contoller (via Notification & Key Value Observing)