我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

admin 2023年12月23日01:02:41评论22 views字数 13637阅读45分27秒阅读模式

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

图1.跟我女儿一起进行后期用户测试

这是一个关于灵感的故事,一个愿景的形成,以及来自最挑剔的客户——一个 2 岁女孩的快速反馈。
想跳到最终产品吗?现在就从App Store下载Aviator — Radar on your Phone吧!

启发
今年夏天,我带着我蹒跚学步的女儿出国了。
她太兴奋了。但是,为了确保她能够应付3个小时的飞行时间,我和我的妻子对她大肆宣传飞机旅行带来的奇妙体验。当我们不得不坐上去机场的出租车时,我的女儿的感到很不情愿,她说希望从我们家走到飞机场上飞机(她不喜欢坐出租车)。

一旦我们登上飞机,事情就发生了令人难以置信的转变——事实证明,如果机组人员发现你和一个可爱的痴迷于飞机的蹒跚学步的孩子在一起,他们会邀请你进去看看驾驶舱。
寻找飞机
这点燃了我女儿对飞机的痴迷。她一直可爱地要求我在天空中为她寻找飞机,当我为她找到飞机时,她很高兴。
上周,我们在花园里呆了一个小时,她坐在我的肩膀上,发现飞机在傍晚的天空中一个接一个地闪烁。

问题出现
虽然和女儿一起玩总是很棒的,但我知道我们可以采用更有效的方法。
我找到了 FlightRadar24这个app,它显示了叠加在地图上的飞机的位置。这很好,但有一点需要让人忍受的是——你不得不调整自己的方向来确定在天空中看哪里,这真的有点烦人

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

在地图上显示飞机的FlightRadar24软件
在二维平面视角上发现飞机的确非常困难。一架 Learjet 在 40,000 英尺高空与刚刚在伦敦城市机场起飞的空中客车相同,但在真实天空中发现巨型喷气式飞机要容易得多(有长长的烟雾尾巴)。
最后,也是最重要的一点,我的孩子并不真正理解或关心地图是什么,她只是想看看飞机。
所以我们有我们的问题。
  • 如何显示方向
  • 采用什么尺寸(缩放的比例)。
  • 要简单易用。
作为一名非物理科学的app技术开发人员,我不知道从哪里开始为我的孩子打造一匹摇摆的木马,但没有什么能阻挡我让她成为一个很酷的应用程序。
构想成形
我有了关于这个应用程序的初步想法:
在雷达上显示附近的航班。
为了符合预期,我们先研究创建工作的要求:
  1. 该应用程序需要保持正确的方向,与设备一起旋转,以便以正确的方向显示飞机。
  2. 该应用程序必须根据飞机的高度显示飞机的大小。
  3. 该应用程序必须有趣,并且感觉更像是一个复古的儿童玩具,而不是一个严肃的商业应用程序。
这些需求,驱动产生了一些构成概念验证的活动部件:
  1. 保持方向是区分产品的核心要求,因为现有解决方案中缺少这一点。我不从事详细的飞行信息——我只想做一个很酷的雷达!iOS Core Location API 为我们提供了帮助,每次用户重新调整设备方向时都会提供委托回调。
  2. 当然,最重要的组件是飞行数据API。OpenSky Network 正是我需要的。一个简单的 REST API,免费用于非商业用途,具有某个地区航班的实时数据。我们希望每隔几秒钟对这个端点执行一次 ping 操作,以获得真实的雷达扫描。
  3. 要调用 API,我们需要一些位置数据。核心位置再次为我们提供了保障——为了获得大量附近的航班,我们可以从用户的位置查询 +/- 1 度的纬度,精度为 0.1 度(约 10 公里),以确保用户的位置被充分混淆。我们也只需要在每个会话中获取一次此数据。
  4. 最后,也是最困难的事情,我们需要掸掉我们的三角学技能,将飞行位置数据与我们自己的定向坐标进行比较。这将使我们能够根据附近的飞机在天空中的相对位置,在正确的位置将附近的飞机吸附到屏幕上
由于我不打算在这个应用程序上建立业务——同样,OpenSky Network API 仅限于非商业用途——我可能会使用 SwiftUI 非常简单的 MV 架构。我会在视图中保留一些业务逻辑,依靠 SwiftUI 的内置 API 来完成繁重的工作,并考虑 API 和位置等核心服务。
一旦我证明了这个概念,我就可以开始做真正有趣的部分——把它变成一个很酷的雷达,并与我的孩子一起测试它!
概念验证
首先是第一件事。
对于图标设计,我们已有有了吉祥物——我正在描绘我女儿戴着可爱的飞行员帽的卡通形象。因此,我们已经有了应用程序名称:Aviator。
凭借我无限的意志力,在 MVP(最小可运行产品) 完成之前,我不会在应用程序图标上浪费时间。但是我现在有一个项目名称可以开始使用。
获取方向
我区分产品的第一个关键要求是保持方向——为了有用,屏幕上的物体需要与它们在现实生活中的位置相对应。因此,当用户旋转时,屏幕本身会旋转并一直指向北方。
暂时忽略模板文件 AviatorApp ContentView ,我启动了一个单例 LocationManager 并从 连接 didUpdateHeading 方法 CLLocationManagerDelegate 。
在导航中,航向是船只(在本例中为 iPhone)指向的指南针方向。
My LocationManager 还处理请求位置权限、设置委托和告诉 Core Location 开始发送方向信息的初始设置。
final class LocationManager: CLLocationManager, CLLocationManagerDelegate {            static let shared = LocationManager()        private(set) var rotationAngleSubject = CurrentValueSubject<Double, Never>(0)        override private init() {        super.init()        requestWhenInUseAuthorization()        delegate = self        startUpdatingHeading()    }        func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {        rotationAngleSubject.send(-newHeading.magneticHeading)    }}
为了使 SwiftUI 视图能够很好地发挥作用,我将通过 Combine 发布者发送方向信息。rotationAngleSubject 这意味着我可以在我的视图中使用 .onReceive 被动地处理它, rotationAngle 并设置一个本地 @State 属性 。
在我看来,为了获得漂亮的指南针效果,我绘制了一组随此 rotationAngle 变化的矩形。
@State private var rotationAngle: Angle = .degrees(0)

var body: some View {    ZStack {        ForEach(0..<36) {            let angle = Angle.degrees(Double($0 * 10)) + rotationAngle            Rectangle()                .frame(width: $0 == 0 ? 16 : 8, height: $0 == 0 ? 3 : 2)                .foregroundColor($0 == 0 ? .red : .blue)                .rotationEffect(angle)                .offset(x: 120 * cos(CGFloat(angle.radians)), y: 120 * sin(CGFloat(angle.radians)))                .animation(.bouncy, value: rotationAngle)        }    }    .onReceive(LocationManager.shared.rotationAngleSubject) { angle in        rotationAngle = Angle.degrees(angle)    }}

在我的设备上进行测试,它看起来相当不错,并且完美地响应了我的真实位置!

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

这就引出了一个问题,为什么谷歌地图无法计算出我面对的方向?

你会注意到一个有趣的视觉故障,因为动画逻辑将 0 度和 360 度视为单独的数字——当我经过正北时,所有的矩形都决定旋转——但这对 PoC 来说很好(因为我不太可能真正保留这个 UI)。

航班数据 API
现在我的热身结束了。
接下来是真正重要的部分:从 OpenSky Network API 解析数据。
它允许您指定纬度和经度范围,并通过简单的 GET 请求返回该范围内的本地航班数组——这意味着您可以简单地将其粘贴到浏览器中,以找出我可以在头顶上看到哪些航班:
https://opensky-network.org/api/states/all?lamin=51.0&lamax=52.0&lomin=-0.5&lomax=0.5

REST API 记录得很好,但具有未键控的结构,这意味着数据按顺序显示为列表属性。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

来自 OpenSky Network API 的 JSON 响应文档

我们需要使用 an UnkeyedContainer 来解码它,它旨在按顺序解析 JSON 响应中的字段。

struct Flight: Decodable {

    let icao24: String     let callsign: String?    let origin_country: String?     let time_position: Int?    let last_contact: Int    let longitude: Double    let latitude: Double

    // ... 

    init(from decoder: Decoder) throws {        var container = try decoder.unkeyedContainer()        icao24 = try container.decode(String.self)        callsign = try? container.decode(String?.self)        origin_country = try container.decode(String.self)        time_position = try? container.decode(Int?.self)        last_contact = try container.decode(Int.self)        longitude = try container.decode(Double.self)        latitude = try container.decode(Double.self)

        // ...    }}

我们可以编写一个简单的 API,根据用户的位置坐标执行 GET 请求。

final class FlightAPI {        func fetchLocalFlightData(coordinate: CLLocationCoordinate2D) async throws -> [Flight] {                let lamin = String(format: "%.1f", coordinate.latitude - 0.25)        let lamax = String(format: "%.1f", coordinate.latitude + 0.25)        let lomin = String(format: "%.1f", coordinate.longitude - 0.5)        let lomax = String(format: "%.1f", coordinate.longitude + 0.5)

        let url = URL(string: "https://opensky-network.org/api/states/all?lamin=(lamin)&lamax=(lamax)&lomin=(lomin)&lomax=(lomax)")!        let data = try await URLSession.shared.data(from: url).0        return try JSONDecoder().decode([Flight].self, from: data)    }}

您可能会注意到,我在此 API 调用中使用了 1 度的经度范围,但只有 0.5 度的纬度。这是因为在我的纬度英国,一个 0.5 纬度 x 1 经度的矩形大致显示为正方形。

现在我们到达了某个地方!
飞行数据被很好地解析为内存 Flight 对象数组,这些对象现在很好,易于处理。

绘制飞机

修改我的 LocationManager 以侦听重大位置更改并通过发布者发送这些坐标是非常微不足道的。

同样,在纯粹的 MV 架构风格中,我的视图通过侦听坐标, .onReceive 并用这些坐标调用我的 new FlightAPI 。结果呢?有关本地天空中高架飞机的数据。

现在,我们到达了我最初概念验证中最困难的部分:相对于我自己的位置,实际将飞机图标显示在正确的位置。

我的第一次迭代是一个钝器:我将相对纬度和经度乘以屏幕上的硬编码点值。

@State private var coordinates: CLLocationCoordinate2D?@State private var flights: [Flight] = []

private var airplanes: some View {    ForEach(flights, id: .icao24) { flight in        let latDiff = coordinate.latitude - (flight.latitude ?? 0)        let lngDiff = coordinate.longitude - (flight.longitude ?? 0)        Image(systemName: "airplane")            .resizable()            .frame(width: 20, height: 20)            .rotationEffect(.degrees(flight.true_track ?? 0))            .foregroundColor(.red)            .offset(x: 250 * latDiff, y: 250 * lngDiff)    }}

当然,这不可能是准确的,因为纬度或经度的绝对距离会随着您的地理位置而变化。但同样,这是一个很好的起点。

初步结果
如何实际测试飞机图纸的准确性?
我可以在一切下面画一张地图!
现在我有 AviatorView 3 层:顶部的指南针、绘制到屏幕上的飞机以及下方朴素的 SwiftUI Map 视图。
@State private var cameraPosition: MapCameraPosition = .camera(MapCamera(        centerCoordinate: CLLocationCoordinate2D(latitude: 51.0, longitude: 0.0),        distance: 100_000,        heading: 0))

var body: some View {    ZStack {        Map(position: $cameraPosition) { }         airplanes        compass    }}

这是我第一次深夜黑客马拉松的结果,与FlightRadar的预测进行了比较。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

第 #1 天的结果,左边是我的应用程序,与右边的 FlightRadar 进行比较

我当然在做一些事情,因为天空中飞机的数量和集群看起来差不多——但位置相差甚远。
突然,又一个灵感闪现。就是这么简单。我简直不敢相信我以前没有想到它。
我需要使用注释在地图上绘制飞机!
The MVP
这个想法已经酝酿了一整天:我们将使用 Map ,并在其精确地理位置的顶部绘制飞机形状的注释。
最终,我想找到一种方法来隐藏实际地图,只将飞机显示为雷达位置上的标记。这应该会给我们带来我们所追求的酷炫、全定向的雷达效果。
地图注记
在我打算瞄准的 iOS 17 中,在地图上绘制注释是一件轻而易举的事。让我们重构一个 FlightMapView.
import MapKitimport SwiftUI

struct FlightMapView: View {        @Binding var cameraPosition: MapCameraPosition        let flights: [Flight]

    var body: some View {        Map(position: $cameraPosition) {            planeMapAnnotations        }        .mapStyle(.imagery)        .allowsHitTesting(false)    }}
在这里,出于雷达的目的,我们希望防止命中测试——也就是说,我们不希望地图是交互式的。在我们的理想世界中,地图是不可见的,用户只能看到航班及其位置。
飞机缩放
在定向之后,尺寸是下一个核心问题,现有解决方案根本无法很好地处理。
我使用飞行高度在地图注释中添加了一些简单的对数缩放,因此更高的飞机在屏幕上看起来更大。
此外,我还使用了飞机的 true_track 属性,并结合了用户在核心位置的方向,以显示面向正确方向的飞机。
@State private var rotationAngle: Angle = .degrees(0)

private var planeMapAnnotations: some MapContent {    ForEach(flights, id: .icao24) { flight in        Annotation(flight.icao24, coordinate: flight.coordinate) {            let rotation = rotationAngle.degrees + flight.true_track            let scale = min(2, max(log10(height + 1), 0.5))            Image(systemName: "airplane")                .rotationEffect(.degrees(rotation))                .scaleEffect(scale)            }        }        .tint(.white)    }}
用户研究
现在是进行终极测试的时候了,看看我的 MVP 是否真的有效。
我要和女儿一起去观察飞机
我们有真实的地图说明,并在地图上显示用户的位置并指示飞机所在的方向。
最重要的是,它准确地找到了飞机!

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

我们通过Aviator发现的第一架飞机,恰如其分地命名为3c65d4
MVP取得了巨大的成功,因为我和我的女儿在应用程序上发现了一架飞机!
这个初步测试还产生了 2 条重要信息:
首先,我的缩放逻辑是倒退的——看看伦敦城市机场地面上的那架小飞机。由于该应用程序的重点是在天空中定位飞机,因此我们需要反转缩放。下层的平面必须显示为更大,因为我们是用眼睛来发现它们的。
其次,我蹒跚学步的孩子不在乎地图,只关心飞机。如果我想清除噪音并专注于发现飞机,我需要删除地图。并开始建立我的雷达!
更新了缩放逻辑
我轻而易举地修复了飞机的缩放逻辑。
经过一番反复试验——为了既能看到屏幕上看起来不错,又能给出合理的尺寸分布,我选择了这个进行缩放:
min(2, max(4.7 - log10(flight.geo_altitude + 1), 0.7))
这些缩放来自我的本地开销扫描:
Scale:  1.0835408863965839Scale:  0.8330645861650874Scale:  1.095791123396205Scale:  1.1077242935783653Scale:  2.0Scale:  1.4864702267977097Scale:  0.7
这种分布效果很好——除了氮氧化物之外,它在航空旅行枢纽的生活中非常有用。
创建雷达
我几乎准备好建造我所设想的雷达。但是有一个问题。
稳健性
开源 OpenSky API 不断超时,返回 502 个错误的网关错误,有时只是生成 200 个响应,其中包含空数据。
弗兰奇,这对我来说很好——这不是一个企业业务应用程序,这个伟大的 API 让我不花钱。他们没有 SLA,我觉得没有资格获得 SLA。
了帮助提高客户端的健壮性,我在 API 调用中实现了一些基本的重试逻辑。
private func fetchFlights(at coordinate: CLLocationCoordinate2D, retries: Int = 3) async {    do {        try await api.fetchLocalFlightData(coordinate: coordinate)

    } catch {        if retries > 0 {            try await fetchFlights(at: coordinate, retries: retries - 1)        }    }}
第二天,API 一整天都运行良好——除了某些高流量时段外,它似乎大部分时间都很好。
覆盖地图
最重要的降噪任务是使实际地图不可见。没有这个,雷达将无法工作。
我能够使用平面颜色来做到这一点 MapPolygon ——表面上是这样设计的,所以你可以放置叠加层来突出显示地图的各个部分。但我想用它来隐藏除我们的注释之外的所有内容。
struct FlightMapView: View {

    var body: some View {        Map(position: $cameraPosition) {            planeMapAnnotations            MapPolygon(overlay(coordinate: coordinate))        }        .mapStyle(.imagery)        .allowsHitTesting(false)    }

    // ...        private func rectangle(around coordinate: CLLocationCoordinate2D) -> [CLLocationCoordinate2D] {        [            CLLocationCoordinate2D(latitude: coordinate.latitude - 1, longitude: coordinate.longitude - 1),            CLLocationCoordinate2D(latitude: coordinate.latitude - 1, longitude: coordinate.longitude + 1),            CLLocationCoordinate2D(latitude: coordinate.latitude + 1, longitude: coordinate.longitude + 1),            CLLocationCoordinate2D(latitude: coordinate.latitude + 1, longitude: coordinate.longitude - 1)        ]    }        private func overlay(coordinate: CLLocationCoordinate2D) -> MKPolygon {        let rectangle = rectangle(around: coordinate)        return MKPolygon(coordinates: rectangle, count: rectangle.count)    }}
利用益减少的好运气储备,这种方法奏效了!我们现在可以看到飞机,但没有地图,就像我们想要的那样!
至关重要的是,苹果将叠加层设计为放置在地图顶部但注释下方。如果他们以其他方式这样做,我女儿的新玩具就会步履蹒跚。
绘制雷达
我的核心要求的最后一部分是雷达视图。
这本质上是一组线、同心圆和 20 度旋转角梯度。对于像我这样的 SwiftUI 爱好者来说,这很简单。
深夜交叉检查
看看我们已经走了多远。
随着今天的核心视觉变化——通过叠加层隐藏地图,以及雷达的几行 SwiftUI 视图——我们现在正在迅速接近我们最初的愿景。
将生成的雷达UI与头顶天空中的飞机进行比较,我们非常匹配。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

第 3 天结果 — 显示 Sidcup 上空的航班
用户测试 #2
经过 3 个晚上的辛勤工作,我蹒跚学步的孩子终于开始对我创造的玩具表现出一些兴趣。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

我们看到了它探测到的飞机!但是,由于我的iPhone相机过时,您必须相信我的话
我们已经证明了这个概念,并制定了一个 MVP,以实现我们设定的核心初始目标。
现在,我们可以开始考虑将其放在 App Store 上。
从MVP到产品
我是拟物化的忠实粉丝。因此,我想发挥我所有的动画力量,让这个应用程序具有我所设想的复古、玩具般的品质。
让雷达转动起来
我为我在雷达上产生的效果感到自豪。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

改善雷达上逼真的淡出效果
这种实现就是我所说的“愚蠢的天才”。
最初,我考虑使用三角函数和计时器——每当线条碰到单个地图注释时,都会重新着色和淡出它们。
但后来我意识到我的线只是一个 20 度宽的角度梯度,从绿色到透明。
如果是 360 度宽角渐变呢?
如果这个渐变从绿色到透明,到透明,到透明,到透明,再到黑色呢?
private var radarLine: some View {    Circle()        .fill(            AngularGradient(                gradient: Gradient(colors: [                    Color.black, Color.black, Color.black, Color.black,                    Color.black.opacity(0.8), Color.black.opacity(0.6),                    Color.black.opacity(0.4), Color.black.opacity(0.2),                    Color.clear, Color.clear, Color.clear, Color.clear,                    Color.clear, Color.clear, Color.clear, Color.clear,                    Color.clear, Color.clear, Color.clear, Color.green]),                center: .center,                startAngle: .degrees(rotationDegree),                endAngle: .degrees(rotationDegree + 360)            )        )        .rotationEffect(Angle(degrees: rotationDegree))        .animation(.linear(duration: 6).repeatForever(autoreverses: false), value: rotationDegree)}
通常情况下,粗暴的解决方案效果最好。
我还发现,当设备旋转得太快时,地图中的一些奇怪的视觉伪影出现在屏幕的角落——叠加层似乎懒洋洋地渲染在地图的相机位置之外。
使用反向掩模创建雷达视图的黑色轮廓解决了该问题(即带有雷达圆孔的黑色矩形)。
让雷达真正转动起来
我们的用户界面现在看起来很整洁。但我还不能称它为复古。
我想添加一个带有电视扫描线的 CRT 屏幕效果,使应用程序看起来像是真的在旧的雷达扫描仪上绘制的。
iOS 17 内置了对 Metal 着色器的支持 colorEffect ,因此它比以往任何时候都更容易实现这种效果。
#include <metal_stdlib>using namespace metal;

[[ stitchable ]] half4 crtScreen(    float2 position,    half4 color,    float time) {        if (all(abs(color.rgb - half3(0.0, 0.0, 0.0)) < half3(0.01, 0.01, 0.01))) {        return color;    }        const half scanlineIntensity = 0.2;    const half scanlineFrequency = 400.0;    half scanlineValue = sin((position.y + time * 10.0) * scanlineFrequency * 3.14159h) * scanlineIntensity;    return half4(color.rgb - scanlineValue, color.a);}
我可能会节省另一篇文章对 C++ 的深入研究。随意窃取它 - 最重要的是,我创建了一个视图修改器,可以将 CRT 效果应用于我们喜欢的任何视图!
extension View {        func crtScreenEffect(startTime: Date) -> some View {        modifier(CRTScreen(startTime: startTime))    }}

struct CRTScreen: ViewModifier {        let startTime: Date        func body(content: Content) -> some View {        content            .colorEffect(                ShaderLibrary.crtScreen(                    .float(startTime.timeIntervalSinceNow)                )            )    }}
请注意,此修改器和着色器本身会采用一个 time 参数,使扫描线快速向上移动,并使效果更加动态。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

实际上,在实现时间调制之前,我将其记录下来并进行了 gif 处理——见下文!

非商业用途
虽然 OpenSky Network 网站非常清晰,但我想礼貌一点,并发送了一张便条,以确保我的 App Store 列表在他们的政策下没问题。
他们在20分钟内非常友好地回复了!

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

超越视觉效果
为了帮助推销雷达的体验,也有助于触摸可访问性,每当航班更新时,我都会添加一点哔哔声系统音效。
private func fetchFlights(coordinate: Coordinate, retries: Int = 2) async {    do {        let flights = try await api.fetchLocalFlightData(coordinate: coordinate)        await MainActor.run {            self.flights = flights            AudioServicesPlaySystemSound(1052)            hapticTrigger.toggle()        }

    // ...

}
除了一些触觉的主视图上的新 sensoryFeedback 修饰符:
.sensoryFeedback(.levelChange, trigger: hapticTrigger)
然而,我现在意识到,这种哔哔声可能会让某些人感到烦恼。所以我应该添加一些自定义选项。
控制与定制
首先,静音模式是有序的。
但是,也许还有一些简单的其他自定义 @AppStorage 。
@AppStorage("silent") var silentMode: Bool = false@AppStorage("showMap") var showMap: Bool = false@AppStorage("userColor") var userColor: Color = .green
现在,用户可以关闭声音,甚至可以关闭雷达覆盖层以查看下面的地图。
然而,最重要的是,由于我正在为我的孩子构建这个,因此通过 SwiftUI 颜色选择器为雷达选择自定义颜色是绝对强制性的。
最后,没有一两个动画 SFSymbol 的生活是什么?
private func toggleableIcon(state: Bool, iconTrue: String, iconFalse: String) -> some View {        Image(systemName: state ? iconTrue : iconFalse)            .contentTransition(.symbolEffect(.replace))    // ...}
我认为我们的应用程序现在已经为黄金时段做好了准备。


我需要做一些重构,将视图移动到它们自己的文件中。
现在,顶层 AviatorView 看起来有点像这样:
// @State properties ...

var body: some View {    ZStack {        if let coordinate = locationManager.coordinateSubject.value {            FlightMapView(                cameraPosition: $cameraPosition,                flights: flights,                rotationAngle: rotationAngle,                coordinate: coordinate            )        }            TimelineView(.animation) { context in            RadarView()                .crtScreenEffect()                .negativeHighlight()        }            ControlsView(errorMessage: errorMessage)    }

    // onRecieve modifiers ...}
飞行员吉祥物
令人讨厌的是,我上个月停止向 Midjourney 付款,所以我在 Gencraft 买了一个免费的非商业用途的发电机。
幸运的是,我设法让我的女儿戴着飞行员帽,这正是我想要的样子!

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

Aviator应用程序徽标,使用Gencraft创建
这也导致了我有史以来最成功的推文。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

9 个赞。考虑到我不为蓝色付费,这很好。欢迎关注!
前往 App Store!
多年来,我没有亲自为 Apple Developer Program 付费。
看看这个废弃的副业项目的墓地。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

RevisionApp 将永远是那个逃脱的人......

呜。我首付 79 英镑,准备发布。

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

有趣的事实:
我只针对 iOS 17。但我仍然需要提供 6.5 英寸和 5.5 英寸 iPhone 的屏幕截图。最新的 5.5 英寸 iPhone?The 8 Plus.它具有 iOS 16 的最高版本。是的。幸运的是,AppScreens 的好人允许我导出两种尺寸。但不要让我开始重新缩放视频。
我们的最终用户测试
在我们等待 Apple 应用程序审查发挥其魔力的同时,让我们与我蹒跚学步的孩子再进行几轮周末用户测试,她非常高兴她现在可以为 UI 选择自己的颜色。

星期六上午

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

周六晚上

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

周日下午

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

而且它是在线的!

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

Aviator v1.0.0 应用商店详情
想自己下载应用程序吗?
立即前往 Aviator — 手机上的雷达(别忘了评分)!
后续步骤
我对我在一周内的几个晚上所做的工作感到非常满意。我已经很久没有接手副业了,为我的女儿制作一个有趣的玩具是我多年来编码中最有趣的。
在写完这篇文章之后,我在下一个版本的迷你路线图中考虑了一些功能:
向地图添加缩放级别,以将雷达限制为仅靠近的飞机。
  • 使用 OpenSky Network API 的高级版本来显示直升机、卫星和飞机尺寸等级。
  • 切换飞机上的始发地和目的地国家/地区显示。
  • 使用更高级的金属着色器改进 CRT 屏幕效果。
  • 将所有控件重构为可调整大小的渐进式披露拉出模式,并带有棘爪。
  • 实施滑块控制以过滤掉某些距离和高度 - 例如,隐藏所有低空,远处的飞机。
  • 实现“滑稽模式”,在雷达上呈现不明飞行物、巨型虫子和外星人。
如果您有任何自己的想法,或者只是一些反馈,请在评论中告诉我!
文章来源:https://jacobbartlett.substack.com/p/my-toddler-loves-planes-so-i-built

我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

原文始发于微信公众号(黑客与极客):我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月23日01:02:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   我为喜欢飞机的女儿做了一个雷达APP,来发现天空中的飞机https://cn-sec.com/archives/2328694.html

发表评论

匿名网友 填写信息