⬆️ ⬇️

Angstrom. A bunch of difficulties in a simple wrapper



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:





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:





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:





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:





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:





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!

Source: https://habr.com/ru/post/278695/



All Articles