When is another bike needed?
Angstrom, of course, if you look at the function performed, the bike. How many ways to convert units? Lot. You can use Google, you can one of the hundreds of applications for iOS or Android.
But at the same time, not a single method solved one problem. How do I get the result of the conversion when I watch the show? Specifically,
Mythbusters . They always talk there about feet and pounds. How much it? Is it a big apartment, 500 ft²? (not really, as it turned out) Is it a lot, 27 psi (yep, dofiga)? And finally, tell them that the Fahrenheits are generally not clear to anyone!
With conventional converters, you have to stop the video, find out what category it is, “psi”, then look for this very “pounds per square inch” there, remember what number you need to enter, understand what to translate it into (to realize the scale of the problem). I want to do it with the device that is at hand, preferably without the Internet.
')
And this problem can not be solved by any converter. I tried, probably a hundred. It is solved by Google, but this is also slow (I launched the browser, entered something in the line, did not understand Google, or understood not so ...).
So does Angstrom bike? It seems not.
Now let's look at the difficulties that had to be solved during its development. Technical difficulties, programmer.
Briefly about the design
A significant part of the complexity has grown out of the great design that Ilya Birman invented. Without it, Angstrom would be called GeeKonv and would look something like this:
Instead, it turned out the application, which is nice to watch, and which is convenient to use.
A little about what issues had to be solved about the UI, you can read from Ilya
in the section of the blog about Angstrom , and I will continue about the technique.
Formula Calculation
Converting units is a difficult task. Firstly, it is a lot of units and it is difficult not to be mistaken with all coefficients and transformations. Secondly, some transformations are nontrivial. If converting from feet to meters requires dividing by one coefficient and then multiplying by another, for example, to translate Fahrenheit to Celsius, you need to try a little more.
Calculating formulas is not an easy task. They need to somehow write, somehow parsit, somehow substitute variables, and so on. Fortunately, in the process of research, I came across an article about the side property of
NSExpression
, which allows you to calculate some arithmetic expressions. It works like this:
[[NSExpression expressionWithFormat:@"(23-7.5)*40.0/21.0+273.15"] expressionValueWithObject:nil context:nil]
And it allows you to calculate something simple (as in the example), or, using the functions
listed in the documentation , a little more complicated.
When working with calculations via
NSNumber
and
NSExpression
, you need to remember to determine the behavior of
NSNumber
in case of incorrect results. Otherwise, the calculations will break. This is done using this code:
[NSDecimalNumber setDefaultBehavior:, NSDecimalNumberHandler]
I return the values ​​there so that nothing breaks, and at the same time I get errors in the log.
@implementation CONDecimalNumberHandler - (NSRoundingMode)roundingMode { return NSRoundBankers; } - (short)scale { return NSDecimalNoScale; } - (NSDecimalNumber *)exceptionDuringOperation:(SEL)operation error:(NSCalculationError)error leftOperand:(NSDecimalNumber *)leftOperand rightOperand:(NSDecimalNumber *)rightOperand { NSLog(@"Error during parsing number: %@/%@ (%d)", leftOperand, rightOperand, (int) error); if (error == NSCalculationOverflow || error == NSCalculationUnderflow) { return [[NSDecimalNumber alloc] initWithString:@"0"]; } else { return [[NSDecimalNumber alloc] initWithString:@"1"]; } } @end
Storage of tables with units
The lists of units themselves are also not easy to store. After all, you need:
- Understand the coefficients or formulas for each.
- Keep priorities so that you can choose the most correct unit for each case.
- For the same type of measurement system is stored units (SI or imperial, for example).
- Translations of unit names are made for each language and for all possible word forms (for Russian, for example, five word forms). This includes the possible symbols for the unit, synonyms for the names (ar or weave or square decameter).
- Some units are combined into “clusters”, for example, 1 meter is converted to feet, and 10 meters - in yards. The second unit is selected from the cluster.
- Finally, the units themselves must be grouped into categories in order to display correctly in the menu, and take this information into account when formatting / translating units.
Initially, the units I store as text files (it is easier to edit them), separately - basic information, localization for each language separately. Here is a file for speeds (free).
knot kn,kt 0.514444 3 impAdd # 11 - — 295 / 1062 /. Mach M 295.0464 2 other speed of light c 299792458 0.11 siAdd meter per minute m/min 60 0 siAdd2 centimeter per second cm/s 100 0 siAdd2 ^minutes per kilometer min/km FORMULAE(16.666666667/X,16.666666667/X) 0 other ^minutes per mile min/mi FORMULAE(26.805555556/X,26.805555556/X) 0 other
From them I get JSON-files that were originally used for work. Unfortunately, this was not enough flexibility and speed. Therefore, now all the data is packed into the SQLite database and read from there as necessary. The base format repeats, more or less, the structure of the JSON file, which was this:
[ { "fml": "", "abbrs": [ "m\/s" ], "us": "si", "id": 1, "tag": "meter per second", "pri": 3, "to": 2000002, "cof": 1, "names": [ "meter per second" ] }, ... ]
In addition to the database with the main data, you still need a search tree. Angstrom can work in two modes:
- translation within the application, when there are three lines "number", "one times", "one two". In this case, you need to find two units in rows, and then calculate the result. Recognition goes, as usual. We run along a tree with dips, in the nodes of which the letters live, and a list of units is attached to each of the nodes. We ran to the desired node, got a list of units, displayed the options (or took the first one if only one is needed).
- line translation. For example, we dictated something (on the clock, or using standard dictation), or copy-paste from another application. In this case, the procedure is approximately as follows:
- Break the string into words
- We clean out too much, prepare, make the first portion of magic (for example, we replace second, since the system recognizes this not as a “second”, but as a “second”, which is usually wrong).
- Parsim number. Here the second portion of magic is applied. The fact is that
NSNumberFormatter
can parse lines-like-numbers, for example, it can recognize as “thirty-four” as “34”. This is a super feature, which is incredibly difficult to use correctly, since we have not just a number, but a string from which this number needs to be extracted. You have to run through the words using ever expanding ranges to try to parse the largest possible number. - The remaining words are trying to parse into units, either one or two. There is the most magic here, since the user can speak as he wants, in any order. We have to create a bunch of heuristics that correctly respond to certain marker words. The basis here is still the same search for the unit tree as in the normal version.
Both of these modes use the unit tree. You could use standard text search, for example, from SQLite, but then you would have to mess around with tokenizers and settings, so I decided to just write my own. The difficulty here and there is similar, but with mine I have more opportunities for optimization.
Nodes in the search tree are stored in separate files. Here are these (I took a very short one):
{"p":"","u":{"":[[1,13,631]]},"s":{},"f":""}
This allows you not to store it entirely in memory, loading as needed. The tree is big, and it greatly speeds up the launch, work on older devices (Angstrom works fine on iPhone 4) and reduces the load on the memory.
I packed the
DPLPacker',
which is written in my article about
iTrace . There are almost 7500 of them at the moment, and it would have been very bad without the packaging.
All the information about the units in Angstrom is now about five megabytes, and the application file in the store is 13.2 megabytes. Immediately obvious, the application - about the transfer of units :)
Optimization
Optimization is a question that does not always arise before application developers. The speed of development of technology allows sometimes either just to score on it, or to do "something simple", and that's enough. Angstrom also has to be used in quite extreme conditions, for example, on the Apple Watch, or on an old iPhone 4 (while iOS 7 is supported). These devices have little memory and a relatively slow processor. Therefore, it is necessary to optimize everything, and at the same time not to forget that in the future there may be 10 times more different units (now there are about 1050 of them).
There are three main points to optimize:
- Start application. To debug the start, we use Instruments, and we throw out everything we can from the start. All updates - start a couple of seconds after the start. No downloads of units, except those on the screen, no heavy resources.
- Search unit. For this, I did a search tree, and broke the information into separate files. They entered a letter - they downloaded exactly the file about this letter, nothing more. In the files themselves, the information is stored in a very compact form (aids, simple lists).
- Memory usage. The techniques here are similar to the previous point, since the main consumer of memory is just the base unit. SQLite, along with a search tree that is fragmented by files, solves the problem well.
Even before the development of Angstrom, I learned that it is very convenient when there is a task that is extremely limited in some resource. For example, for the application to work on the Apple Watch, you need to optimize the speed, the first version of the watch is very, very slow, and parsing has a natural text, it takes significant time. Also, in version 1.8, the parsing occurs in several languages ​​at once, so that you can dictate in Russian, even if the interface is in English (the dictation itself does not give any indication of what language is currently being used). Optimization for such a "bad" device improves performance for the rest, more modern and faster.
To make Today Extension (now it is turned off, as it is buggy and does not work well), the most severe optimization from memory was required. I wanted him to parse a string from the clipboard (and not just show a couple of lines), this required a fully-fledged application. The funny thing is that Apple is talking about the amount of memory available for this kind of expansion. “Not enough,” they say. How much is it enough? “The smaller the better!” No specific numbers. Therefore, the optimization of memory - to the limit.
This all brings the optimization requirements to an extremum. We have to use all known techniques, invent new data structures (more precisely, use well-forgotten old ones), stick a stick at parallelism, look attentively at what Instruments shows, throw out algorithms from time to time that hinder more complex, but also more efficient.
I even advise sometimes, if you are developing an application, take the most braking device that is, and run, drive on it. I have a special for this and the iPhone 4, and the fifth-generation iPod Touch (there is the same hardware as the iPhone 4S). The first one is almost irrelevant, but the second one will be relevant for another year and a half (everything stands on it, including the latest for today iOS 9).
Equipment. Appearance
I already wrote a little about the appearance. For example, you can read about the
correct rounding of corners in my blog, there is also about
testing UI . But there are several points that I have not yet described.
Keyboard
The keyboard in Angstrom is always shown, except when it is not shown. It moves (try to right to the right of the first screen), it disappears (connect an external keyboard or hide it on the ipad), and it can be different (iphone / ipad / ipad pro). Our digital keyboard is also attached to it, which is narrow
It happens high
It happens Aypadnaya
In version 1.8, she learned how to switch in order to be able to enter hexadecimal or Roman numerals. Summing up, everything is difficult.
I will tell you about two things. How to move the keyboard, and how to make the bass (our digital) work in Accessibility.
To move the keyboard, you need to understand the device windows in iOS. Each application has its own window (UIWindow), but if modal dialogs or keyboards appear, the number of windows increases. If you use something like
Reveal , then the hierarchy is visible very well:
In the picture (from far to near) layers:
- Uiscreen
- main application window
- window in which the system sticks custom keyboard
- keyboard window
You can see right away (I love Reveal for that), what and how you need to move in order to work. As a result, I move the main window as I want (I have complete control over it), I too can at least get my keyboard from the links and move:
_keypadView.transform = CGAffineTransformMakeTranslation(_keypadView.virtualFramePositionX, 0);
I can get the keyboard window by going through all the application windows, or simply by trying to get the last window if it looks like a keyboard:
NSArray *windows = [UIApplication sharedApplication].windows; if ([NSStringFromClass([windows.lastObject class]) contains:@"Keyboard"]) { _keyboardWindow = windows.lastObject; }
Why I do not use enumeration of all windows in the application every time, caching value? This is quite slow in iOS 9 (and earlier it was normal). Therefore it is necessary to optimize (the code should work 60 frames per second, with interactive svaypah). To speed up, I also check that the frame of the window has really changed and update it exclusively in the right situation. And not the frame, but only the center, since the window sizes always remain the same, and changing the frame can lead to much more serious changes than just changing the position of the view.
If you move the keyboard like me, then be prepared for glitches. Glitches in each version of iOS are different, they appear in:
- No keyboard in modal dialogs. For example, if in Ebout Angstrem open the creation of a letter, there may be no keyboard (or maybe) in the dialogue. It was in older versions of iOS, when the same keyboard was used for all windows. They left alone, all the others left. Need to return.
- Shifted elements. In the same letter creation dialog, for example, a
UIMenuController
can move. - In difficult cases of working with the keyboard (left, it seemed for the modal controller, returned) - the keyboard may disappear. Apparently, the keyboard window itself is recreated from time to time and the old link is lost.
Also be prepared for the fact that the keyboard may disappear (or another one will appear). For example, when purchasing an extended set of units, a system keyboard appears for entering a password. After completion of the purchase procedure (no matter how it is completed), you need to return the keyboard back.
In general, the recommendation is simple. Do not touch the keyboard for no reason, let the system do it. Otherwise plunge, as I plunge, there is a lot of rake.
Accessibility
I really wanted our kipad, even if not looking like a standard keyboard, to behave in a similar way. At first I tried to find out how to play the sound of a pressed key. Joyful, I learned about
UIInputViewAudioFeedback
, and about the
[[UIDevice currentDevice] playInputClick]
, which does exactly what you need.
After that, I needed to support accessibility, that is, the work of the application, when it is used by users with disabilities. For ordinary interface components, nothing is easier. We set for component a few, and all.
self.isAccessibilityElement = YES; self.accessibilityLabel = @" "; self.accessibilityHint = @", "; self.accessibilityValue = @"";
With kipad everything turned out to be more difficult. I draw it entirely to make it easier to draw the background gradient, and in older versions to round the corners.
In order to maintain both keyboard behavior (clicks) and everything else, we had to create transparent
UIButton,
buttons over the drawn keyboard
UIButton,
which values ​​are correctly written, and carefully monitor that they change when the keyboard is changed (in the latest version, Roman input and hexadecimal numbers).
If the Accessibility topic is interesting to you, I can tell you more about it. Or you can look at the relevant sessions with the WWDC, they are very good:
iOS Accessibility and
Apple Watch Accessibility
Accessibility also helped me in testing, which I described
in more detail
in my blog .
Questions?
Maybe they are interested in some other features or implementation details? Ask!