IOS implements Pulse Radar and dynamic addition and deletion elements By Swift and iosswift
Before getting started
After Swift is updated with version Xcode6 Beta4, it can basically be used as a production tool. Although there are some places that are "lagging behind" compared with ObjC, it is also harmless. Here we use Xcode6 Beta4 + iOS SDK 8.0 for development. If we use ObjC, we only need to replace some syntax and call methods. Final effect:
This effect is converted from MOV to GIF, and CSDN does not support uploading images larger than 2 MB,Youku addressCreate basic animation
This effect is converted from MOV file to GIF, and CSDN does not support uploading images larger than 2 MB. Youku address
Create a Single View Application project and create a Swift file. The created Project is "PulsingRadarView". The current structure is as follows:
In ViewController, it holds an Optional PulsingRadarView attribute, indicating that it can be nil, and then implements a simple initialization in viewDidLoad:
class ViewController: UIViewController { var radarView: PulsingRadarView? override func viewDidLoad() { super.viewDidLoad() let radarSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.width) radarView = PulsingRadarView(frame: CGRectMake(0,(self.view.bounds.size.height-radarSize.height)/2, radarSize.width,radarSize.height)) self.view.addSubview(radarView) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. }}
The radar is circular, so the width and height are self. view. bounds. size. width.
The PulsingRadarView should be empty now. We first import QuartzCore, because CALayer is used in the animation section later, and then override the drawRect method:
override func drawRect(rect: CGRect) { UIColor.whiteColor().setFill() UIRectFill(rect) let pulsingCount = 6 let animationDuration: Double = 4 var animationLayer = CALayer() for var i = 0; i < pulsingCount; i++ { var pulsingLayer = CALayer() pulsingLayer.frame = CGRectMake(0, 0, rect.size.width, rect.size.height) pulsingLayer.borderColor = UIColor.grayColor().CGColor pulsingLayer.borderWidth = 1 pulsingLayer.cornerRadius = rect.size.height / 2 var defaultCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault) var animationGroup = CAAnimationGroup() animationGroup.fillMode = kCAFillModeBackwards animationGroup.beginTime = CACurrentMediaTime() + Double(i) * animationDuration / Double(pulsingCount) animationGroup.duration = animationDuration animationGroup.repeatCount = HUGE animationGroup.timingFunction = defaultCurve var scaleAnimation = CABasicAnimation(keyPath: "transform.scale") scaleAnimation.autoreverses = false scaleAnimation.fromValue = Double(0) scaleAnimation.toValue = Double(1.5) var opacityAnimation = CAKeyframeAnimation(keyPath: "opacity") opacityAnimation.values = [Double(1),Double(0.7),Double(0)] opacityAnimation.keyTimes = [Double(0),Double(0.5),Double(1)] animationGroup.animations = [scaleAnimation,opacityAnimation] pulsingLayer.addAnimation(animationGroup, forKey: "pulsing") animationLayer.addSublayer(pulsingLayer) } self.layer.addSublayer(animationLayer)}
First, set the background color of the canvas to white, pulsingCount to the number of waveforms, and animationDuration to the animation duration. Then I created an animationLayer to store all animation layers ------ pulsingLayer, in this way, the layer structure looks like:
Each pulsingLayer represents a circle. in the loop, initialize the pulsingLayer first: Set the frame, border color, border size, and radius. radius is naturally half of its width or height.
CAMediaTimingFunction will be available later.
Next, create an animation group, because we need two animations: scale and opacity, and we need to control the animation start time.
We use several images in the Controlling Animation Timing Article to illustrate the attributes of fillMode and beginTime:
Each square below represents 1 second. The figure below represents 4 seconds. The animation time is 1.5 seconds. The yellow color indicates the animation start, the blue indicates the animation end, and the yellow to the blue indicates the animation process. As you can see, the blue part is white after it ends, which means that the animation ends and is removed from the layer.
The animation start time in the following figure is offset by 1 second, And the rest remain unchanged.
By default, all layers have the same timeline regardless of the order in which they are created. The beginTime is 0, it indicates that the animation is started immediately after the Layer is added (or the animation is played at the current time). to offset the animation by 1 second (for example), cacur=mediatime () + 1 to obtain the current systemAbsolute Time(Seconds) and + 1. To achieve the pulse effect, we need to make the animation of each animationGroup start at different beginTime, so we need to set beginTime = CACurrentMediaTime () + Double (I) * animationDuration/Double (pulsingCount ), swift does not support implicit type conversion. It is strongly converted using Double.
However, we can see that there is an empty file before the animation starts after the offset, which is determined by fillMode:
- Default Value of kCAFillModeRemoved. Before and after the animation starts, the animation has no effect on the layer.
- KCAFillModeForwards when the animation ends, the layer will remain in the final state of the animation.
- KCAFillModeBackwards and kCAFillModeForwards. For more information, see
- KCAFillModeBoth kCAFillModeForwards and kCAFillModeBackwards
In our current situation, the pulsingLayer is set to frame and border. Therefore, during the blank animation period, the pulsingLayer will directly display a circle with a border (the animation has not yet started ), of course, after an animation is played once, the border will not be displayed, because it enters a normal animation playback loop and there will be no blank periods. We only need to avoid having to leave the animation blank before playing, that is, set fillMode = kCAFillModeBackwards (entering the animation state in advance ).
RepeatCount = HUGE is the literal meaning, indicating the infinite loop of the animation (HUGE can be considered as infinite, if it is ObjC, use HUGE_VAL ). CAMediaTimingFunction is provided by the system with the following values:
- KCAMediaTimingFunctionLinear linear, that is, constant speed
- KCAMediaTimingFunctionEaseIn is slow and fast
- KCAMediaTimingFunctionEaseOut
- KCAMediaTimingFunctionEaseInEaseOut
- The actual effect of kCAMediaTimingFunctionDefault is that it is faster at the start of the animation and slows down at the end of the animation.
CAMediaTimingFunction supports customization. We set the timingFunction to kCAMediaTimingFunctionDefault to make the animation play more dynamic.
The following Scale animation is very simple. It can be changed from 0 (0 times) to 1.5 (1.5 times. Opacity transparent animation only needs to set values and its corresponding keyTimes. Note that keyTimes indicates the time ratio, and the value ranges from 0 to 1, for example, if the first element of values is 1 and the first element of keyTimes is 0, the opacity is 1 at the beginning of the animation, the second element of values is 0.7, and the second element of keyTimes is 0.5, indicates that when the animation is played in half, the opacity is 0.7, and so on. Then, encapsulate the individual scale animation and opacity animation into the animationGroup, and add the animationGroup containing the two animations to the pulsingLayer and animationLayer, add the animation Layer that contains all animation layers.
Dynamic addition and deletion Elements
This effect is converted from MOV file to GIF, and CSDN does not support uploading images larger than 2 MB. Youku address
The animation part has been completed. Next we will add an interface to PulsingRadarView to enable it to add or remove elements. First, add two attributes to PulsingRadarView:
class PulsingRadarView: UIView { let itemSize = CGSizeMake(44, 44) var items = NSMutableArray()
The first is the size of each item, and the second is used to store all items. Add addOrReplaceItem public interface:
public func addOrReplaceItem() { let maxCount = 10 var radarButton = UIButton(frame: CGRectMake(0, 0, itemSize.width, itemSize.height)) radarButton.setImage(UIImage(named: "UK"), forState: UIControlState.Normal) var center = generateCenterPointInRadar() radarButton.center = CGPointMake(center.x, center.y) self.addSubview(radarButton) items.addObject(radarButton) if items.count > maxCount { var view = items.objectAtIndex(0) as UIView view.removeFromSuperview() items.removeObject(view) }}
MaxCount is the maximum value of an item displayed in a circle. It is simply written to death. You can open it to become a public attribute. Every item here is UIButton. After initialization, you can set an image. The generateCenterPointInRadar method returns the Center Coordinate of a circle, which is generated only within the diameter of the circle and will be released later. Finally, determine whether the maxCount is exceeded. If it is exceeded, remove the first added item. Before releasing the generateCenterPointInRadar method, we must first understand which range is the range of coordinate generation:
As we all know, the basic shape of the View is the rectangle (Red area), and the drawRect is based on the Rect, but our radar is circular, that is, the blue area is our target range, so the generated coordinates should be centered around the central Green Point (center of the circle). Let's re-open the mathematics textbook and look at the definition of trigonometric function in high school mathematics:
In A plane Cartesian coordinate system, draw A circle with the center of the origin and the radius of 1. the x axis of this circle is located at point. Take O as the center of rotation, rotate A certain angle α to B point counterclockwise, and set the coordinates of B point to (x, y ), at this time, the value of y is called the sine of α and recorded as sin α. At this time, the value of x is called the cosine of α and recorded as cos α; the ratio of y to x y/x is called the tangent of α, which is recorded as tan α.
There is also an important formula:
Parameter equation of a circle: the Circle centered on O (a, B) and with r as the radius is x = a + r * cos θ, y = B + r * sin θ, where θ is a parameter)So far, the idea is clear. The following is the implementation of the generateCenterPointInRadar method:
private func generateCenterPointInRadar() -> CGPoint{ var angle = Double(arc4random()) % var radius = Double(arc4random()) % (Double)((self.bounds.size.width - itemSize.width)/2) var x = cos(angle) * radius var y = sin(angle) * radius return CGPointMake(CGFloat(x) + self.bounds.size.width / 2, CGFloat(y) + self.bounds.size.height / 2)}
We first randomly generate an angle within (
θ), And then a random value is generated within the radius range, which is treated as a new radius r. using the formula, we get the points of x and y with a center (a, B) as a helper, a coordinate can be generated, which is based on the center of the circle when returned, therefore, we can directly use the addOrReplaceItem interface as the center after getting the coordinates. This is actually a fully-used formula algorithm. In this way, the addOrReplaceItem interface is complete, and the call in ViewController is also improved. Specifically, a Timer is added at the end of the viewDidLoad method, this Timer calls addOrReplaceItem every 0.5 seconds:
NSTimer.scheduledTimerWithTimeInterval(0.5, target: radarView, selector: Selector("addOrReplaceItem"), userInfo: nil, repeats: true)
Timer must call the invalidate () method when it is not used, and before the ViewController structure, otherwise the ViewController will not be released and will never be destructed. We will not consider that much here. After all, there is only one page and it will not be used in real scenarios. More often, it will be handled during network request callback. In this way, the dynamic increase and decrease are completed, but is it perfect? Apparently not.
Optimization optimization is not so much about optimization as fixing bugs. Obviously, in the previous step, the dynamically generated elements overlap, which is unacceptable, and we only need to make some changes to prevent this situation. When we generate the center of each item, we do not compare it with the existing item. This is a performance-consuming operation. If your itemSize is too large and maxCount is too large, this can even lead to a lethal loop. In that case, you may limit the itemSize and maxCount, and control the number of loops, if the center of an item is generated with too many loops, it can be regarded as an endless loop. In this case, you can only recalculate the existing center.
S. This is not an extreme case here, because the current itemSize and maxCount are used together, there will be no endless loops. We add a private itemFrameIntersectsInOtherItem method to determine whether it overlaps with the previously generated center:
private func itemFrameIntersectsInOtherItem (frame: CGRect) -> Bool { for item in items { if CGRectIntersectsRect(item.frame, frame) { return true } } return false}
Receives a frame and compares it with each item. If it overlaps, true is returned. Otherwise, false is returned. Transformation in the addOrReplaceItem method:
...do { var center = generateCenterPointInRadar() radarButton.center = CGPointMake(center.x, center.y)} while (itemFrameIntersectsInOtherItem(radarButton.frame))...
Wrap the center with a do-while loop. In this way, the generated elements will not overlap.
Optimization 2 I plan to add an animation effect to the display and removal of each item to avoid being too stiff and using the derived class method:
class PRButton: UIButton { init(frame: CGRect) { super.init(frame: frame) self.alpha = 0 } override func didMoveToWindow() { super.didMoveToWindow() if self.window { UIView.animateWithDuration(1, animations: { self.alpha = 1 }) } }}
Replace UIButton in addOrReplaceItem with PRButton, so that when the item is added, there is a simple transition animation. When I was about to rewrite removeFromSuperview, I encountered a problem:
In Swift, the closure cannot use super, so it can only be like this:
override func removeFromSuperview() { UIView.beginAnimations("", context: nil) UIView.setAnimationDuration(1) self.alpha = 0 UIView.setAnimationDidStopSelector(Selector("callSuperRemoveFromSuperview")) UIView.commitAnimations()}private func callSuperRemoveFromSuperview() { super.removeFromSuperview()}
You can see the complete effect after running.
Optimization 3 also fixes bugs. If you press the Home key during animation playback (the simulator presses command + shift + h), the following occurs:
This is because when you press the Home key, all animations are removed. Specifically, each Layer calls the removeAllAnimations method. If we want to continue the animation when we return to the application, we need to listen to the system's
UIApplicationDidBecomeActiveNotificationNotification:
...weak var animationLayer: CALayer?init(frame: CGRect) { super.init(frame: frame) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("resume"), name: UIApplicationDidBecomeActiveNotification, object: nil)}func resume() { if self.animationLayer { self.animationLayer?.removeFromSuperlayer() self.setNeedsDisplay() }}deinit { NSNotificationCenter.defaultCenter().removeObserver(self)}
In this way, the animation can start again when it returns to the application. I reference animationLaye in weak for better judgment.
If you have any questions in this article, please point them out in time to avoid unnecessary troubles for later users. Thank you!
Can someone briefly describe how to use MATLAB to study the relationship between the ranging precision, bandwidth, and repetition frequency of radar pulse signals? Now
Www.sotichina.com