Experts say: This is an iOS development skill that you can’t miss (Part 3)

Experts say: This is an iOS development skill that you can’t miss (Part 3)

Swift 2 is out, and we have to keep up with the times, otherwise we will become an antique. Besides, it is open source, and there is something to do. Being a programmer is really tiring, always chasing, but never catching up. Just when I have a little idea, others step on the accelerator again.

This issue has three main contents:

Tint Color

Build Configurations in Swift

Keyboard events

Tint Color

After iOS 7, UIView added a new tintColor property, which defines a non-default tint color value. The setting of its value will affect the entire view hierarchy with the view as the root view. It is mainly applied to some controls such as app icons, navigation bars, buttons, etc. to obtain some interesting visual effects.

The declaration of the tintColor property is as follows:

  1. var tintColor: UIColor!

By default, the tintColor of a view is nil, which means that the view will use the tint color value of the parent view. When we specify the tintColor of a view, this color value is automatically propagated to all subviews in the view hierarchy (with the current view as the root view). If the system does not find a non-default tintColor value in the view hierarchy, the system-defined color value (blue, RGB value [0,0.478431,1], which we can see in IB) is used. Therefore, this value always returns a color value, that is, we did not specify it.

Related to the tintColor property is the tintAdjustmentMode property, which is an enumeration value that defines the adjustment mode of the tint color. Its declaration is as follows:

  1. var tintAdjustmentMode: UIViewTintAdjustmentMode

The definition of enumeration UIViewTintAdjustmentMode is as follows:

  1. enum UIViewTintAdjustmentMode : Int {
  2. case Automatic // The view's shading adjustment mode is consistent with the parent view  
  3. case Normal // The view's tintColor property returns the view's tint color completely unmodified  
  4. case Dimmed // The view's tintColor property returns a desaturated, darkened view tint color  
  5. }

Therefore, when the tintAdjustmentMode property is set to Dimmed, the color value of tintColor is automatically dimmed. If we do not find a default value in the view hierarchy, the value defaults to Normal.

There is also a tintColorDidChange method related to tintColor, which is declared as follows:

  1. func tintColorDidChange()

This method is automatically called when the tintColor or tintAdjustmentMode property of the view changes. In addition, this method is also called if the tintColor or tintAdjustmentMode property of the parent view of the current view changes. We can refresh our view as needed in this method.

Example

Next, let’s take a look at the powerful function of tintColor through an example (the example is plagiarized from an example written by Sam Davies, which can be found in iOS7 Day-by-Day :: Day 6 :: Tint Color. I was responsible for moving bricks and implemented it in Swift. The code can be downloaded here).

Let’s take a look at the final effect first (the following are all stolen pictures, please forgive me, I’m too lazy):

The elements of this interface mainly include UIButton, UISlider, UIProgressView, UIStepper, UIImageView, ToolBar and a custom subview CustomView. Next, let's see what effect changing the view's tintColor will have on these controls.

In the viewDidLoad method of ViewController, we set the following:

  1. override func viewDidLoad() {
  2. super .viewDidLoad()
  3.  
  4. println( "\(self.view.tintAdjustmentMode.rawValue)" ) // Output: 1  
  5. println( "\(self.view.tintColor)" ) // Output: UIDeviceRGBColorSpace 0 0.478431 1 1  
  6.  
  7. self.view.tintAdjustmentMode = .Normal
  8. self.dimTintSwitch?.on = false  
  9.  
  10. // Load the image  
  11. var shinobiHead = UIImage(named: "shinobihead" )
  12. // Set the rendering mode  
  13. shinobiHead = shinobiHead?.imageWithRenderingMode(.AlwaysTemplate)
  14.  
  15. self.tintedImageView?.image = shinobiHead
  16. self.tintedImageView?.contentMode = .ScaleAspectFit
  17. }

First, we try to print the default tintColor and tintAdjustmentMode, which output [UIDeviceRGBColorSpace 0 0.478431 1 1] and 1 respectively. This is the output when we do not set any tint color related values ​​for the entire view hierarchy. You can see that although we did not set tintColor, it still returns the system's default value; and tintAdjustmentMode returns the original value of Normal by default.

Next, we explicitly set the value of tintAdjustmentMode to Normal and set the image and rendering mode of UIImageView.

When we click the "Change Color" button, the following event handling method will be executed:

  1. @IBAction func changeColorHandler(sender: AnyObject) {
  2.  
  3. let hue = CGFloat(arc4random() % 256 ) / 256.0  
  4. let saturation = CGFloat(arc4random() % 128 ) / 256.0 + 0.5  
  5. let brightness = CGFloat(arc4random() % 128 ) / 256.0 + 0.5  
  6.  
  7. let color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0 )
  8. self.view.tintColor = color
  9. updateViewConstraints()
  10. }
  11.  
  12. private func updateProgressViewTint() {
  13. self.progressView?.progressTintColor = self.view.tintColor
  14. }

This code mainly generates a color value randomly and assigns it to the tintColor property of self.view, while updating the tintColor value of the progress bar.

Note: The tint color of certain components of some controls is controlled by specific properties. For example, progress has two tint colors: one for the progress bar itself and the other for the background.

Click the "Change Color" button to get the following effect:

As you can see, we did not manually set the color values ​​of UIButton, UISlider, UIStepper, UIImageView, ToolBar and other subviews in the example, but as the color value of the tintColor attribute of self.view changes, the appearance of these controls also changes. That is to say, the change of the color value of the tintColor attribute of self.view affects the appearance of all subviews in the entire view hierarchy with self.view as the root view.

It seems that tintColor is still very powerful.

There is also a UISwitch in the interface, which is used to turn on and off the dim tint function. The corresponding processing method is as follows:

  1. @IBAction func dimTimtHandler(sender: AnyObject) {
  2. if let isOn = self.dimTintSwitch?.on {
  3.  
  4. self.view.tintAdjustmentMode = isOn ? .Dimmed : .Normal
  5. }
  6.  
  7. updateViewConstraints()
  8. }

When tintAdjustmentMode is set to Dimmed, the actual effect is that the entire color value becomes darker (there is no picture to steal here).

In addition, we rewrite the tintColorDidChange method in the subview CustomView to listen to the changes in tintColor to update our custom view. Its implementation is as follows:

  1. override func tintColorDidChange() {
  2. tintColorLabel.textColor = self.tintColor
  3. tintColorBlock.backgroundColor = self.tintColor
  4. }

Therefore, the color of the box and "Tint color label" changes with the tintColor of the subview, and the tintColor of the subview is inherited from the parent view.

In this example, the most interesting part is the image processing. The image processing is relatively simple and crude. For a pixel, if its alpha value is 1, its color is set to tint color; if it is not 1, it is set to transparent. This is how the ninja avatar in the example is processed. However, we need to set the imageWithRenderingMode property of the image to AlwaysTemplate, so that when rendering the image, it will be rendered as a template and its color information will be ignored, as shown in the code:

var shinobiHead = UIImage(named: "shinobihead")

// Set the rendering mode

shinobiHead = shinobiHead?.imageWithRenderingMode(.AlwaysTemplate)

Off topic

A digression that has little to do with the topic.

In color theory, a tint color is a mixture of a color and white. Similar to this are shade color and tone color. Shade color is a mixture of a color and black, and tone color is a mixture of a color and gray. They are all based on Hues. The effects of these color values ​​are shown in the following figure:

[[139242]]

For some basic theoretical knowledge, you can refer to Hues, Tints, Tones and Shades: What's the Difference? or some more professional articles.

summary

If we want to specify the tint color of the entire App, we can set the tint color of the window. In this way, all subviews under the same window will inherit this tint color.

When an alert or action sheet pops up, iOS7 will automatically darken the tint color of the view behind it. At this time, we can override the tintColorDidChange method in the custom view to perform the desired operation.

Some complex controls can have multiple tint colors, with different tint colors for different parts of the control, such as the UIProgressView mentioned above, as well as navigation bars, tab bars, toolbars, search bars, scope bars, etc. The background tint color of these controls can be handled using the barTintColor property.

#p#

Build Configurations in Swift

In Objective-C, we often use preprocessor directives to help us execute different codes according to different platforms, so that our code supports different platforms, such as:

  1. # if TARGET_OS_IPHONE
  2.  
  3. #define MAS_VIEW UIView
  4.  
  5. #elif TARGET_OS_MAC
  6.  
  7. #define MAS_VIEW NSView
  8.  
  9. #endif

In Swift, since the support for C language is not as friendly as Objective-C (I don’t know how Swift 2 supports C yet), we cannot use preprocessing instructions as freely and comfortably as in Objective-C.

However, Swift also provides its own way to support conditional compilation, using build configurations. Build configurations already include literals true and false, as well as two platform test functions os() and arch().

os() is used to test the system type. The parameters that can be passed in include OSX, iOS, and watchOS. So the above code can be changed to:

  1. # if os(iOS)
  2. typealias MAS_VIEW = UIView
  3. #elseif os(OSX)
  4. typealias MAS_VIEW = NSView
  5. #endif

Note: In the WWDC 2014 session “Sharing code between iOS and OS X” (session 233), Elizabeth Reid refers to this approach as shimming.

Unfortunately, os() can only detect the system type, but not the system version, so these tasks can only be handled at runtime. As for how to detect the system version, Mattt Thompson gave us the answer in his article Swift System Version Checking.

Let's take a look at arch(). arch() is used to test the CPU architecture. The values ​​that can be passed in include x86_64, arm, arm64, and i386. It should be noted that arch(arm) will not return true for ARM 64 devices. However, arch(i386) will return true when compiled on a 32-bit iOS simulator.

If we want to customize some compilation configuration options used during debugging, we can use the -D flag to tell the compiler. The specific operation is to add the required configuration options in "Build Setting" -> "Swift Compiler-Custom Flags" -> "Other Swift Flags" -> "Debug". If we want to add commonly used DEGUB options, we can add "-D DEBUG" here. In this way, we can perform some different operations in the code for debug and release, such as

  1. # if DEBUG
  2. let totalSeconds = totalMinutes
  3. # else  
  4. let totalSeconds = totalMinutes * 60  
  5. #endif
  6.  
  7. A simple conditional compilation statement looks like this:
  8.  
  9. # if build configuration
  10. statements
  11. # else  
  12. statements
  13. #endif

Of course, statements can contain 0 or more valid Swift statements, which can include expressions, statements, and control flow statements. In addition, we can also use the && and || operators to combine multiple build configurations, and we can use the ! operator to negate the build configuration, as shown below:

  1. # if build configuration && !build configuration
  2. statements
  3. #elseif build configuration
  4. statements
  5. # else  
  6. statements
  7. #endif

It is important to note that in Swift, conditional compilation statements must be syntactically valid, because Swift will syntax check them even if the code will not be compiled.

#p#

Keyboard events

In interfaces involving form input, we usually need to listen to some keyboard events and perform corresponding operations according to actual needs. For example, when the keyboard pops up, we need to shrink our UIScrollView automatically so that the entire UIScrollView content can be seen. To this end, the following 6 notification constants are defined in UIWindow.h to handle keyboard events at different time points:

  1. UIKeyboardWillShowNotification // Before the keyboard is displayed  
  2. UIKeyboardDidShowNotification //After the keyboard is displayed  
  3. UIKeyboardWillHideNotification // Before the keyboard is hidden  
  4. UIKeyboardDidHideNotification // After the keyboard message  
  5. UIKeyboardWillChangeFrameNotification // Before the keyboard size changes  
  6. UIKeyboardDidChangeFrameNotification // After the keyboard size changes  
  7.  
  8. The object objects of these notifications are all nil. The userInfo dictionary contains some keyboard information, mainly the position and size of the keyboard. We can get the corresponding value in the dictionary by using the following key:
  9.  
  10. // The frame of the keyboard before the animation starts  
  11. let UIKeyboardFrameBeginUserInfoKey: String
  12.  
  13. // The frame of the keyboard behind the animation harness  
  14. let UIKeyboardFrameEndUserInfoKey: String
  15.  
  16. //Keyboard animation curve  
  17. let UIKeyboardAnimationCurveUserInfoKey: String
  18.  
  19. //Keyboard animation time  
  20. let UIKeyboardAnimationDurationUserInfoKey: String

Here, I am interested in the order in which keyboard events are called and how to get the size of the keyboard to resize the view appropriately.

From the defined keyboard notification types, we can see that we are actually concerned with keyboard events in three stages: display, hide, and size change. Here we set two UITextFields with different keyboard types: one is a normal keyboard and the other is a numeric keyboard. We monitor all keyboard events and print related logs (I won't post the code here) to see the results directly.

1) When we let textField1 get the input focus, the printed log is as follows:

  1. keyboard will change
  2. keyboard will show
  3. keyboard did change
  4. keyboard did show

2) Without hiding the keyboard, let textField2 get the focus, and the printed log is as follows:

  1. keyboard will change
  2. keyboard will show
  3. keyboard did change
  4. keyboard did show

3) Close the keyboard again, and the printed log is as follows:

  1. keyboard will change
  2. keyboard will hide
  3. keyboard did change
  4. keyboard did hide

From the above log, we can see that no matter whether the keyboard is shown or hidden, a size change notification will be sent, and it will be before the corresponding events of show and hide. When switching between keyboards of different sizes, in addition to sending change events, show events will also be sent (hide events will not be sent).

There are two more points to note:

If you switch between two keyboards of the same size, no message will be sent.

If it is a switch between Chinese and English keyboards in a normal keyboard, as long as the size changes, one or more groups of messages with the same process as 2) above will be sent

Knowing the order of event calls, we can decide in which message processing method to perform operations according to our needs. To do this, we need to obtain some useful information. This information is encapsulated in the userInfo of the notification, and the relevant values ​​are obtained through the above constant key. Usually we are concerned about UIKeyboardFrameEndUserInfoKey to obtain the frame of the keyboard after the animation is completed, so as to calculate the height of our scroll view. In addition, we may hope that the change in the height of the scroll view is also transitioned through animation, then UIKeyboardAnimationCurveUserInfoKey and UIKeyboardAnimationDurationUserInfoKey are useful.

We can get these values ​​in the following ways:

  1. if let dict = notification.userInfo {
  2.  
  3. var animationDuration: NSTimeInterval = 0  
  4. var animationCurve: UIViewAnimationCurve = .EaseInOut
  5. var keyboardEndFrame: CGRect = CGRectZero
  6.  
  7. dict[UIKeyboardAnimationCurveUserInfoKey]?.getValue(&animationCurve)
  8. dict[UIKeyboardAnimationDurationUserInfoKey]?.getValue(&animationDuration)
  9. dict[UIKeyboardFrameEndUserInfoKey]?.getValue(&keyboardEndFrame)
  10.  
  11. ......
  12. }

In fact, there are three other values ​​in userInfo, but these values ​​have been deprecated since iOS 3.2, so we don't need to pay too much attention to them.

***Let's talk about forms. A form interface looks simple, but interactions and UI can always come up with various ways to make it complicated, and there are actually a lot of details designed into it. Financial apps like ours usually involve a lot of form input, so how to do it well still requires some thought. When I'm free, I plan to summarize and write an article.

#p#

Odds and Ends

Customizing UIPickerView's rows

The main content of UIPickerView is actually not much, mainly a UIPickerView class and the corresponding UIPickerViewDelegate, UIPickerViewDataSource protocol, which represent the agent and data source respectively. I won't go into details here, just answer a small requirement we encountered.

Usually, UIPickerView can define multiple columns, such as year, month, and day. These columns do not interfere with each other and can scroll independently without interfering with others. However, we have a requirement to have three columns, but these three columns need to scroll together. Well, this needs to be handled separately.

  1. In UIPickerViewDelegate, the following delegate method is declared:
  2.  
  3. - (UIView *)pickerView:(UIPickerView *)pickerView
  4. viewForRow:(NSInteger)row
  5. forComponent:(NSInteger)component
  6. reusingView:(UIView *)view

We can use this method to customize the view of the row. It's getting late, so I won't say any more nonsense, let's go straight to the code:

  1. - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
  2.  
  3. PickerViewCell *pickerCell = (PickerViewCell *)view;
  4.  
  5. if (!pickerCell) {
  6.  
  7. NSInteger column = 3 ;
  8.  
  9. pickerCell = [[PickerViewCell alloc] initWithFrame:(CGRect){CGPointZero, [UIScreen mainScreen].bounds.size.width, 45 .0f} column:column];
  10. }
  11.  
  12. [pickerCell setLabelTexts:@[...]];
  13.  
  14. return pickerCell;
  15. }

We define a PickerViewCell view, which places UILabels in columns equally according to the column parameter we pass in, and sets the text of each UILabel through setLabelTexts. Of course, we can also define the appearance of UILabel in PickerViewCell. It's that simple.

However, one thing to note is that although it appears that 3 columns are displayed, it is actually processed as 1 column, so the following implementation should return 1:

  1. - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
  2. return   1 ;
  3. }

Constructing an object of class type '**' with a metatype value must use a 'required' initializer.

How to fix the issue of "[AnyObject]? does not have a member named generator" in Swift

There is a small requirement to traverse all ViewControllers in the current navigation controller stack. The viewControllers property of the UINavigationController class itself returns an [AnyObject]! array, but since my navigation controller itself may be nil, the ViewController array I get is as follows:

  1. var myViewControllers: [AnyObject]? = navigationController?.viewControllers
  2.  
  3. The obtained myViewControllers is an [AnyObject]? optional type. At this time, if I directly traverse myViewControllers, as shown in the following code
  4.  
  5. for controller in myViewControllers {
  6. ...
  7. }

The compiler will report an error with the following prompt:

  1. [AnyObject]? does not have a member named "Generator"  

In fact, this error will be reported whether it is [AnyObject]? or other types such as [String]?. The reason is that the optional type is just a container, it is a different type from the value it wraps, that is, [AnyObject] is an array type, but [AnyObject]? is not an array type. We can iterate an array, but not a non-collection type.

There is such an interesting analogy on stackoverflow, I am lazy and just post it here:

To understand the difference, let me make a real life example: you buy a new TV on ebay, the package is shipped to you, the first thing you do is to check if the package (the optional) is empty (nil). Once you verify that the TV is inside, you have to unwrap it, and put the box aside. You cannot use the TV while it's in the package. Similarly, an optional is a container: it is not the value it contains, and it doesn't have the same type. empty, or it can contain a valid value.

So, the processing here should be:

  1. if let controllers = myViewControllers {
  2. for controller in controllers {
  3. ......
  4. }
  5. }

<<:  Redefining B2D 0HI.CN will be launched on 36kr crowdfunding platform

>>:  Follow the following principles and stop worrying about Xcode code signing issues

Recommend

How much does it cost to customize a fast food mini app in Diqing?

How much is the quote for customized fast food in...

[Kinetic Taping Technology] Kinesiology Taping Video

[Kinetic Taping Technology] Introduction to Kines...

Turning water into "milk" is a basic skill for chefs!

Can water be turned into "milk"? Of cou...

Four important links in content operation, you need to know

Content operation is a painful and joyful thing. ...

Shopping traps that must be avoided on Double Eleven

Another "Double Eleven" is coming. Many...

She is a true "light chaser", making the magic of the sun shine brightly in life

There has never been a lack of great women on the...

There is a big game behind Youpengpule's 500 million advertising bonus

Recently, Shao Yiding, chairman of China's la...

Why does the last 1% of battery in a mobile phone last so long?

How would you feel when your phone only has 1% ba...