-
The Apple Framework Family Tree
It has been almost 4 years since I started to develop iOS apps and recently I made **[Subault](/subault)** which allowed me to broaden my knowledge about Apple Frameworks. Nonetheless, what they have had to offer are numerous, and I bump into something new almost every single day. The other day, I got to use struct **[Morphology](https://developer.apple.com/documentation/foundation/morphology)** to handle the auto pluralization (It was easy thanks to it!). What is trickier is that many frameworks are built on top of the other framework for the most part whether or not it is obvious. For example, SwiftUI. It is very easy to start with, and has been attracting many developers because of its nature, and I am one of them. However, you will soon notice there is hidden UIKit behind the back when your development progress in SwiftUI. This can be real confusing to me for those who started development in the SwiftUI era just like me. So, I created the family tree for Apple Frameworks using GPTs. This simple chart illustrates the chronology of when some pivotal frameworks came out, and the relationship of them. ```markdown # C (1972) ├── Objective-C (1984) │ ├── Foundation Framework (1994) │ │ └── NSString, NSArray, NSDictionary, NSDate │ ├── Core Foundation (1994) │ │ └── CFNetwork, CFArray, CFDictionary, CFString │ ├── AppKit (macOS, 1989) │ │ └── NSWindow, NSView, NSButton, NSTableView │ │ ├── Cocoa (macOS) │ │ └── (Combination of AppKit, Foundation, and Core Data) │ ├── CoreGraphics (2001, Quartz) │ │ └── CGContext, CGPath, CGImage, CGColor │ │ ├── Core Animation │ │ ├── UIKit (iOS, 2008) │ │ │ └── UIView, UIViewController, UITableView, UIButton │ │ │ ├── SwiftUI (2019) │ │ │ └── View, Text, Button, List │ │ ├── AppKit (for macOS UI components) │ ├── CoreAudio (2001) │ │ └── AudioUnit, AudioComponent, AudioStreamBasicDescription, AudioBuffer │ │ ├── AVFoundation (2010) │ │ └── AVPlayer, AVAudioEngine, AVCaptureSession, AVAsset │ ├── WebKit (2003) │ │ └── WKWebView, WKNavigation, WKUserScript, WKScriptMessage │ ├── Core Data (2005) │ │ └── NSManagedObject, NSManagedObjectContext, NSFetchRequest, NSPersistentStore │ ├── CoreLocation (2008) │ │ └── CLLocationManager, CLLocation, CLGeocoder, CLRegion │ │ ├── MapKit (2009) │ │ └── MKMapView, MKAnnotation, MKOverlay, MKDirections │ ├── Metal (2014) │ │ └── MTLDevice, MTLCommandQueue, MTLBuffer, MTLTexture │ │ ├── SceneKit (2012) │ │ │ └── SCNView, SCNScene, SCNNode, SCNGeometry │ │ ├── SpriteKit (2013) │ │ └── SKScene, SKNode, SKSpriteNode, SKAction │ ├── CoreML (2017) │ │ └── MLModel, MLFeatureProvider, MLPredictionOptions, MLUpdateTask │ │ ├── Vision (2017) │ │ └── VNImageRequestHandler, VNDetectFaceRectanglesRequest, VNRecognizeTextRequest, VNDetectBarcodesRequest │ ├── ARKit (2017) │ │ └── ARSession, ARWorldTrackingConfiguration, ARFrame, ARAnchor │ │ ├── RealityKit (2019) │ │ └── RealityKit.Entity, RealityKit.AnchorEntity, RealityKit.ModelEntity, RealityKit.Scene # Swift (2014) ├── Swift Standard Library │ └── Array, Dictionary, Set, String ├── SwiftUI (2019) │ └── View, Text, Button, List │ (Built on top of UIKit for iOS and AppKit for macOS) ├── Combine (2019) │ └── Publisher, Subscriber, Subject, Cancellable ``` This kinda helpled me see the overview of the Apple Frameworks and understand where to start when I get confused or want to look into some underlying technology. I am not an expert at all so I tried to make it as accurate as possible, but there might be possibilities for it to be wrong, so please let me know if you spot something inaccurate.
-
Creating curvy natural movements
Recently, I experimented with playing with MeshGradient, which was introduced in iOS 18. Although Apple briefly talked about how to use it in the talk, which was helpful, I wanted to push it a little further just to get familiar with it and inspire myself for future projects. I don't want to get into too much detail here, so here is the result: just a tiny MeshGradient generator. The concept is simple. You map coordinates based on the specified number of points. Regardless of its simplicity, I was very happy with the result. You can also change the points, colors, and you can show each point's coordinates, which is somehow useful. Every single time I write in SwiftUI, I greatly appreciate how it is so simple and clean. All this was just 2 or 3 hours' worth of work. However, I wanted to push it further to look into the possibilities of visual design. Also, I wanted to train myself to create the logic, and this article is mainly about the implementation and thoughts behind it, so I animated each point to add a lava-lamp-like effect to it. Here is the result. It is beautiful, isn't it? So, to achieve this, I started very simply to see what I could do. (I am not a math genius, so I struggled a lot.) Thankfully, we have TimelineView that continuously emits a date flow, and I decided to go with it. The trick is rudimentary trigonometry, and each time the date is emitted, a random angle is generated. Below is the result. ```swift TimelineView(.animation(paused: paused)) { context in ZStack { ForEach(points.indices, id: \.self) { index in Circle() .fill(.black) .frame(width: 12, height: 6) .position(points[index].controlPoint) .onChange(of: context.date) { _, _ in let random = CGFloat.random(in: 0...CGFloat.pi * 2) let vector = CGVector(dx: cos(random), dy: sin(random)) withAnimation(.spring) { points[index].controlPoint.x += (vector.dx * amplitude) points[index].controlPoint.y += (vector.dy * amplitude) if points[index].controlPoint.x < initialPoint.x - limit { points[index].controlPoint.x = initialPoint.x - limit } else if points[index].controlPoint.x > initialPoint.x + limit { points[index].controlPoint.x = initialPoint.x + limit } else if points[index].controlPoint.y < initialPoint.y - limit { points[index].controlPoint.y = initialPoint.y - limit } else if points[index].controlPoint.y > initialPoint.y + limit { points[index].controlPoint.y = initialPoint.y + limit } } } } } } ``` It was a bit too random and I needed to elaborate on it more. (With that said, it is fun to watch since it looks like they are bugs trying to run away). What's missing in here is randomness for parameters such as speed and frequency that it takes turn, otherwise, it looks very constant althoug the angle is random (which is okay). I just wondered if I reduce amplitude, it'll solve the problem. but that just make movements slower and has nothing to do with making it look natural, so I had to change model in the way mentioned below. ```swift struct Point { let id: UUID = UUID() var controlPoint: CGPoint = .zero var displacement: CGPoint = .zero var angle: Double = 0.0 var freqency: Int = 0 var speed: CGFloat = 0.0 var xAmplitude: CGFloat = 0.0 var yAmplitude: CGFloat = 0.0 var clockwise: Bool = Bool.random() } ``` I set the five parameters along with angle: frequency, speed, xAmplitude, yAmplitude, clockwise which is boolean to control the direction. Even though you would be confused by the numbers of parameters, the trick is relatively simple. Each date emitted, the angle is added by 0.05 which allows the point move on a perfect circle. However, that's just an infinite movement, so adding randomly generated angle in the range of 0.001 to 0.05 at maximum is needed. After, random speed, xAmplitude and yAmplitude are multiplied, tweaks the direction and speed of the movement. Finally, at a random frequency, each point decides to move clockwise or counterclockwise depending on the frequency and clockwise value. and here is the completed logic below. ```swift func updatePointPosition(index: Int, date: Date) { gradientPoints[index].freqency = Int.random(in: 4...7) if Int(date.timeIntervalSinceReferenceDate) % gradientPoints[index].freqency == 0 { gradientPoints[index].clockwise = Bool.random() } let angle = gradientPoints[index].angle let offset: Double = Double.random(in: 0.001...0.05) let newAngle = gradientPoints[index].clockwise ? angle + offset : angle - offset gradientPoints[index].speed = CGFloat.random(in: 0.01...2.0) gradientPoints[index].xAmplitude = CGFloat.random(in: 0.1...2.0) gradientPoints[index].yAmplitude = CGFloat.random(in: 0.1...2.0) let direction = CGVector(dx: cos(newAngle + CGFloat.random(in: 0.0...0.5)), dy: sin(newAngle + CGFloat.random(in: 0.0...0.5))) gradientPoints[index].displacement = CGPoint( x: direction.dx * gradientPoints[index].speed * gradientPoints[index].xAmplitude, y: direction.dy * gradientPoints[index].speed * gradientPoints[index].yAmplitude ) gradientPoints[index].angle = newAngle withAnimation(.spring(response: 1.0, dampingFraction: 0.5, blendDuration: 0)) { gradientPoints[index].controlPoint.x += gradientPoints[index].displacement.x gradientPoints[index].controlPoint.y += gradientPoints[index].displacement.y if gradientPoints[index].controlPoint.x < initialPoint.x - limit { gradientPoints[index].controlPoint.x = initialPoint.x - limit gradientPoints[index].angle = Double.random(in: 0...Double.pi * 2) } else if gradientPoints[index].controlPoint.x > initialPoint.x + limit { gradientPoints[index].controlPoint.x = initialPoint.x + limit gradientPoints[index].angle = Double.random(in: 0...Double.pi * 2) } else if gradientPoints[index].controlPoint.y < initialPoint.y - limit { gradientPoints[index].controlPoint.y = initialPoint.y - limit gradientPoints[index].angle = Double.random(in: 0...Double.pi * 2) } else if gradientPoints[index].controlPoint.y > initialPoint.y + limit { gradientPoints[index].controlPoint.y = initialPoint.y + limit gradientPoints[index].angle = Double.random(in: 0...Double.pi * 2) } } } ``` and the next is the result. Other noteworthy tricks are how the spring animation used here, how frequent each point should decide whether or not it moves clockwise, and when each point hits the boundary, it tries not to stick at the boundary by generating a new random angle. It looks fun to watch doesn't it? I am aware that there are still things to work on, but I am currently quite happy with what came out. It seems like the result is an elegant movement of firefly while the previous one are just flies. Like I said, it is not perfect and I am pretty confident that there is more efficient way out there. For example, the points collide when they move, which create ugly gradients, also since the points can be also manipulated by the drag gesture, I could not get into shader of any other techniques (Maybe there are ways and I am too dumb to notice them.) But hey, it is completed and I learned something haha. If you read this, and have a question or suggest any better way or techniques, please let me know via email, mastodon, or anything.