こんにちは、エンジニアリング室の太田です!
2024年8月22日(木)から24日(土)までオンライン・オフラインのハイブリッド形式で開催された iOSDC Japan 2024 にて、DeNA は今年もゴールドスポンサーとして協賛しました! また、DeNA は iOSDC Japan 2024 の「スポンサーブース」に出展し、iOS App エンジニアたちが考えた企画、「SwiftUI Crossword Quiz」を実施しました!!
当日はたくさんの方々にご参加いただき、大盛況となりました。「SwiftUI Crossword Quiz」で集めた解答用紙は、254枚にものぼりました! このブログでは、「SwiftUI Crossword Quiz」の問題とその答え、さらに解説を行います。
*本問題の動作環境は iOS 17.5 以上となっています。
Question1の問題と解説
Question 1の問題と実行結果は以下の通りです。出題者は @ojun_9 !
import SwiftUI
struct Question01: View {
var body: some View {
ScrollView(.□□□□□□□□□□□) { // A-3
□□□□□□(spacing: 40) { // A-4
Text("iOSDC Japan 2024")
.bold()
.font(.title)
Text("DeNA Booth")
.font(.title)
}
.padding(.□□□□□□□, 64) // A-1
}
}
}
# □□□□□□□ { // A-2
Question01()
}
解説
この問題はSwiftUIでよく利用するワードが問題になっています。 SwiftUIを利用してアプリを作ったことがある方には馴染みのあるワードだったので非常に答えやすかったのではないかと思います。
Question2の問題と解説
Question 2の問題と実行結果は以下の通りです。出題者は @yaso_san !
import SwiftUI
struct Question02: View {
static let colors: [Color] = [
.green,
.yellow,
.orange,
.red,
.purple,
.blue
]
let stops = {
let diff = 1.0 / Double(colors.count)
var location: CGFloat = 0.0
var stops: [Gradient.Stop] = []
for color in colors {
stops.append(.init(color: color, location: location))
location += diff
stops.append(.init(color: color, location: location))
}
return stops
}()
var body: some □□□□ { // A-5
VStack(spacing: 48) {
Image("MysteriousCharacter")
.resizable()
.renderingMode(.template)
.frame(width: 320, □□□□□□: 320) // A-2
.□□□□□□□□□□Style(LinearGradient(stops: stops, // A-1
startPoint: .top,
endPoint: .□□□□□□)) // A-4
Text("What's his name ?")
.font(.system(size: 40, weight: .□□□□)) // A-3
.foregroundColor(.secondary)
}
}
}
解説
日本のiOSエンジニアの間では有名なあのキャラクターを虹色に彩って表示をします。 虹色といっても7色ではなく6色となっており、敬虔なAppleファンの方ならお気付きかもしれませんが、Appleロゴと同じ配色です。 本当はSF SymbolsのAppleロゴで問題にしたかったのですが、その説明にも書かれている通り使用制限がありますので、今回はuhooi氏に代役を務めてもらいました。
キーポイント
虹色を表現するのにLinearGradientを使っています。LinearGradientは普通に使うと滑らかなグラデーションを描画できますが、Gradient.Stopのyの位置を2つ連続で同じ座標にすることで縞模様を実現しています。
反省点
難易度Easyにしてはforegroundは文字数が多く、ちょっと難しくなってしまったかもしれません。backgroundと同じ文字数で迷ったというご意見もいただいたので、例えばforeは見せてgroundだけ答えてもらうくらいでも良かった気がします。
Question3の問題と解説
Question 3の問題と実行結果は以下の通りです。出題者は 奥瀬 雄哉!
import SwiftUI
struct Question03: View {
let cardTitle = "iOSDC Japan 2024"
let title = "Build with SwiftUI"
let description = "SwiftUI is the best way to build apps across Apple platforms"
var body: some View {
VStack(spacing: 32) {
Rectangle()
.fill(.pink)
.aspectRatio(2, contentMode: .fit)
.□□□□□□□(Text(cardTitle) // A-2
.font(.largeTitle)
.foregroundColor(.white))
.cornerRadius(8)
.□□□□□□(radius: 10) // A-3
.padding(.horizontal)
HStack {
□□□□□(systemName: "swift") // A-1
.resizable()
.frame(width: 48, height: 48)
.□□□□□□□□□□□□□□□(.blue) // A-4
.padding()
□□□□□□(alignment: .leading) { // A-5
Text(title)
.font(.title)
Text(description)
.font(.title3)
}
}
}
}
}
解説
SwiftUIのHStack
とVStack
は、UI構築で頻繁に使用される基本的なコンポーネントです。今回作成したクイズは、この2つと基本的なmodifierを使いながらUI構築を行っています。
反省点
DeprecatedになっているforegroundColorを解答に含めてしまった。
Question4の問題と解説
Question 4の問題と実行結果は以下の通りです。出題者は 山本 ののか!
import Charts
import SwiftUI
struct ChartEntry: Identifiable {
var title: String
var minY: Int
var maxY: Int
var averageY: Int {
(minY + maxY) / 2
}
var id: String { title }
}
struct Question04: View {
let data: [ChartEntry] = [
.init(title: "4/1", minY: 15, maxY: 27),
.init(title: "4/2", minY: 17, maxY: 28),
.init(title: "4/3", minY: 18, maxY: 24),
.init(title: "4/4", minY: 16, maxY: 25),
.init(title: "4/5", minY: 14, maxY: 26),
.init(title: "4/6", minY: 17, maxY: 20),
.init(title: "4/7", minY: 17, maxY: 23)
]
var body: some View {
VStack {
Text("Temperature")
.font(.system(size: 20, weight: .bold))
.□□□□□□□□□□Color(.secondary) // A-4
□□□□ { // A-5
Chart {
ForEach(data) { dataPoint in
AreaMark(x: .value("Date", dataPoint.title),
yStart: .value("Lowest temperature", dataPoint.minY),
yEnd: .value("Highest temperature", dataPoint.maxY))
.foregroundStyle(Color.yellow)
.□□□□□□□(0.3) // A-2
□□□□□□□□(x: .value("Date", dataPoint.title), // A-1
y: .value("Average", dataPoint.averageY))
}
}
.chartYAxis {
AxisMarks(position: .leading) { value in
AxisGridLine()
□□□□Tick() // A-6
Axis□□□□□□□□□□ { // A-3
if let intValue = value.as(Int.self) {
Text("\(intValue)°C")
}
}
}
}
.frame(height: 200)
.chartForegroundStyleScale([
"temperature range": .yellow, "average temperature": .blue
])
}
}
}
}
解説
Chartsを使用して、気温に関するグラフを作成しました。 気温の変動幅を表すAreaMarkと平均温度を表すLineMarkを使用しています。
キーポイント
A-3は、これ自体を知らなくても、これ自体の役割から連想できるかがポイントです。値のlabelという役割を担っていることから”ValueLabel”と入れてしまえば答えになります。 A-6は、気づきにくいですが、グラフの各数値の目盛りを表しています。これ自体を知らなくても、他のコードにも多用されている”Axis”というワードが使われるのではないか、という発想ができるかどうかがポイントです。
反省点
Chartsを使用している方が少なかったので、そもそもパラメータやコンポーネント名を知らなくて答えられないという事象が多く発生してしまいました。 他のコードから連想してみると意外と答えに辿りつけるようにしていたつもりでしたが、難易度が少し高かったかな、と反省しています。
Question5の問題と解説
Question5の問題とPreview結果は以下のとおりです。出題者は @akkiee76 !
import MapKit
import SwiftUI
struct Question05: View {
@State private var isPresented: Bool = true
var body: some View {
VStack {
MapView()
.□□□□□□Ratio(1, contentMode: .fit) // A-2
Spacer()
}
.sheet(isPresented: $isPresented) {
VStack(alignment: .leading) {
Text("Golden Gate Bridge")
.font(.title)
Text("San Francisco, California")
.font(.subheadline)
.foreground□□□□□(.secondary) // A-5
□□□□□□□() // A-6
Text("□□□□□ Golden Gate Bridge") // A-1
.font(.title2)
Text("A suspension bridge spanning the Golden Gate Strait connecting San Francisco Bay on the west coast of the United States with the Pacific Ocean.")
HStack {
Spacer()
Image("GoldenGateBridge")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(8)
Spacer()
}
Spacer()
Text("photo by @mvdheuvel on Unsplash")
.font(.subheadline)
}
.□□□□□□□(.all, 8) // A-3
.presentationDetents([.medium])
}
}
}
struct MapView: View {
// A-4
private let coordinate = CLLocation□□□□□□□□□□2D(latitude: 37.8199, longitude: -122.4783)
private var region: MKCoordinateRegion {
MKCoordinateRegion(center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
}
var body: some View {
□□□(initialPosition: .region(region)) { // A-7
Marker("Golden Gate Bridge", coordinate: coordinate)
}
}
}
解説
MapKitを利用してよくある地図アプリ風の問題にしました。回答部分は基本的なModifierを出題しています。MapKitのキーワードはMapKitを使ったことがなくても回答しやすい問題となっているので、前後のコードから推測してみてください!
Question6の問題と解説
Question 6の問題と実行結果は以下の通りです。出題者は @66nylon_y !
import SwiftUI
struct Question06: View {
var body: some View {
ZStack {
Rectangle()
.fill(.radialGradient(colors: [lightYellow, orange, purple, lightBlue, blue],
center: .top,
startRadius: 0,
endRadius: 190))
YellowToOrangeShape()
.fill(.radialGradient(colors: [lightYellow, orange, paleOrange],
center: .top,
startRadius: 100,
endRadius: 200))
CurvedCenterLineShape()
.fill(.radialGradient(colors: [lightYellow, orange, purple, lightBlue],
center: .top,
startRadius: 30,
endRadius: 200))
Text("□□□□□") // A-1
.font(.largeTitle.bold())
.□□□□□□□□□□□□□□□(.white) // A-4
}
.frame(height: 200)
.mask(Rectangle().frame(height: 200))
}
}
struct ShapeSource {
let center: □□□□□□□ // A-5
let radius: CGFloat
init(rect: CGRect) {
center = CGPoint(x: rect.midX, y: rect.minY - 15)
radius = rect.width / 2 + 120
}
}
struct YellowToOrangeShape: □□□□□ { // A-2
func path(in rect: CGRect) -> Path {
var path = Path()
let source = ShapeSource(rect: rect)
let degrees: [(Double, Double)] = [(10, 20), (45, 85), (155, 165)]
for degree □□ degrees { // A-6
path.move(to: source.center)
path.□□□□□□(center: source.center, // A-7
radius: source.radius,
startAngle: Angle(degrees: degree.0),
endAngle: Angle(degrees: degree.1),
clockwise: false)
}
return path
}
}
struct CurvedCenterLineShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let source = ShapeSource(rect: rect)
path.move(to: source.center)
path.add□□□□Curve(to: CGPoint(x: source.center.x - 30, y: rect.maxY), // A-3
control: CGPoint(x: source.center.x - 33, y: rect.maxY * 0.8))
path.addLine(to: CGPoint(x: source.center.x + 30, y: rect.maxY))
path.add□□□□Curve(to: CGPoint(x: source.center.x + 5, y: rect.minY), // A-3
control: CGPoint(x: source.center.x + 33, y: rect.maxY * 0.75))
return path
}
}
解説
RectangleとPathで書いたViewをZStackで重ねて、macOS Sequoiaを描きました。
キーポイント
黄色からオレンジの層はaddArcを使って円の中の指定された角度内のみ描画して塗りつぶし、中央の黄色からライトブルーのラインはaddQuadCurveを使ってベジェ曲線を追加することで少し丸みを持たせています。
よくあった間違い
addArc、addQuadCurveを知っていないと埋めるのがなかなか難しい。
反省点
最初にキーワードSequoiaにして、Sequoiaを描こう…!というアイデアからそのまま走り出してしまったので、「q」が含まれるメソッドなどを見つけるのにも難航し、「知っているか知っていないか…」という問題になってしまいました、、
Question7の問題と解説
Question 7の問題と実行結果は以下の通りです。出題者は @nekodoko315 !
import MapKit
import SwiftUI
struct Question07: View {
static let tokyoTowerForCameraCoordinate: CLLocationCoordinate2D = .init(latitude: 35.660, longitude: 139.7454)
static let tokyoTowerCoordinate: CLLocationCoordinate2D = .init(latitude: 35.658581, longitude: 139.745438)
@State private var cameraPosition: MapCameraPosition =
.camera(.init(.init(lookingAtCenter: Question07.tokyoTowerForCameraCoordinate,
fromDistance: 1000, pitch: 45,
heading: 0)))
@Namespace var mapScope
var body: some View {
□□□(position: $cameraPosition, scope: mapScope) { // A-7
□□□□□□□□□□(coordinate: Question07.tokyoTowerCoordinate) { // A-6
Image(systemName: "mappin.and.ellipse") // A-2: Image
.foregroundStyle(.white) // A:8: foregroundStyle
.padding(8) // A-4: padding
.background(□□□□□□□□□□□□□□□□(cornerRadius: 8) // A-5
.fill(.blue))
} label: {
Text("東京タワー")
.bold()
.padding([.top], 5)
}
}
.□□□□□□□(alignment: .bottomTrailing) { // A-1
VStack { // A-3: VStack
□□□□□□□□□□(scope: mapScope) // A-9
.mapControlVisibility(.visible)
}
.padding(.trailing, 10)
.buttonBorderShape(.circle)
}
.mapScope(mapScope)
.□□□□□□□□(.standard(elevation: .realistic,
pointsOfInterest: .excludingAll)) // A-3
}
}
解説
Appleのマップ上で東京タワー位置を表示し、東京タワーの経度、緯度にビューを表示してこの位置が東京タワーのことを明示しています。 マップビューの上にいくつかマップに関する機能ボタンを表示していますが、こちらについても全部Apple公式のAPIを利用しています。
キーポイント
Apple公式のMapKitを利用してマップを表示し、ボタンなどについても公式のものだけを使って表示しています。
よくあった間違い
A-6はUIKitのマップでも使われる用語でGoogleMapsではGMSMarkerと呼ばれるものです。
反省点
そもそもMapKitに触れたことがない方が多かったので、次はもっと一般的に使えるものを提案します..
Question8の問題と解説
Question 8の問題と実行結果は以下の通りです。出題者は @the_uhooi !
import SwiftUI
struct Question08: View {
@Environment(\.dismiss) private var dismiss
private let date: Date = .□□□ // A-2
var body: some View {
Form {
LabeledContent("現在日時") {
Text("\(date, format: .dateTime)")
}
LabeledContent("アイコン") {
Image(systemName: "tennis.racket")
.resizable()
.□□□□□□□□□□□() // A-4
.foreground□□□□□(.green) // A-1
}
LabeledContent("角丸四角形") {
Color.blue
.□□□□□□□□□(□□□□□□□□□□□□□□□□(cornerRadius: 8)) // A-3, A-5, 6
.padding()
}
LabeledContent("ヒント") {
Text("SwiftLintのオレオレカスタムルール一覧 by uhooi | トーク | iOSDC Japan 2024 #iosdc - fortee.jp")
}
}
.toolbar {
ToolbarItem(placement: .□□□□□□□□□□□□□□) { // A-7
Button("閉じる", systemImage: "xmark") {
dismiss()
}
}
}
}
}
解説
ウホーイが iOSDC Japan 2024 に寄稿したパンフレット記事を読めばわかる問題になっています。 非推奨になった API を使わず、新しい API を覚えてもらうために作成しました。
Qiita にもほぼ同様の記事を投稿しているので、よかったら読んでみてください。 https://qiita.com/uhooi/items/9deb56416aaa81709f69
よくあった間違い
- A-1: 私がブースに立っているときはなかったが、
foregroundColor()
のミスリードを狙った。foregroundColor()
は非推奨になり、iOS 15.0+ ではforegroundStyle()
を使う
反省点
- A-4: ノーヒントだったので、
scaled□□□□□()
など一部を露出すべきだった - A-7:
.○○Action
も考えられるので、一意に決まりやすいワードを選ぶべきだったかもしれない
Question9の問題と解説
Question 9の問題と実行結果は以下の通りです。出題者は @ojun_9 !
import Foundation
import UserNotifications
// Apple Swift version 6.0 (swiftlang-6.0.0.5.15 clang-1600.0.22.6)
// ちょっとStrict Concurrencyの対応が間に合わない😭
□□□□□□□□□□□□□□□ // A-4
func someFunction() {}
// 対応間に合った😭
func anotherFunction(
completion: □□□□□□□□□ □□□□□□□□□ () -> Void // A-5, A-6
) {
// ...
}
protocol P: Sendable {
func f()
}
// テストをするためにプロパティを持たせたい
// などの理由で どうしても class で実装したい
final class C1: P {
□□□□□□□□□□□□□□□□□□□ var array = [1, 2, 3] // A-1
func f() { /* ... */ }
}
// ビルドが通るように...
extension UNNotification: □□□□□□□□□□□□ □□□□□□□□□□ Sendable {} // A-7, A-2, A-3
解説
この問題はStrict Concurrencyチェックの対応を行う際に、一時的な対応として実装する可能性のあるキーワードを当てる問題になっています。 iOSDC Japan 2024のセッション 座談会 「Strict ConcurrencyとSwift 6が開く新時代: 私たちはどう生きるか?」 を見た方やZennの記事である 事例別!Strict Concurrency対応方法 を読んだ方はスラスラと解けたのではないでしょうか。
キーポイント
キーポイントは、それぞれのキーワードを知っているか・それぞれのキーワードを実際に利用したことがあるかどうかになります。また、キーワードを実際に使ったことがあっても A-1 のような長いキーワードを覚えているかどうかも問題を解く鍵となります。
Question10の問題と解説
Question 10の問題と実行結果は以下の通りです。出題者は @akkiee76 !
import Charts
import SwiftUI
struct Question10: View {
let columns = [GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
Text("Donuts Menu")
.font(.largeTitle)
.padding()
LazyVGrid(columns: columns, □□□□□□□: 10) { // A-3
ForEach(Donuts.allCases, id: \.self) { donut in
VStack {
□□□□□(donut.fravors, id: \.name) { fravor in // A-5
□□□□□□Mark(angle: .value("value", donut.fravorRatio), // A-4
innerRadius: .ratio(0.4), // A-7: innerRadius
angularInset: 2)
.□□□□□□Radius(12) // A-6
.foregroundStyle(by: .value("fravor", fravor.name))
.opacity(0.7)
}
.chartForegroundStyleScale(donut.scales)
.chartLegend(.□□□□□□) // A-8
.rotationEffect(□□□□□(degrees: 30)) // A-1
.frame(height: 100)
.padding(.top, 10)
Text(donut.taste)
.multilineTextAlignment(.center)
.□□□□□□□(.vertical, 8) // A-2
}
}
}
.padding(.horizontal)
}
}
// case planeAndChocolate以降は省略しています。 'case planeAndChocolate' and after are omitted.
enum Donuts: CaseIterable {
case plane
case planeAndChocolate
var taste: String {
switch self {
case .plane: "Plane & Sugar"
case .planeAndChocolate: "Plane & Chocolate"
}
}
var fravors: [Fravor] {
switch self {
case .plane:
[.init("sugar", .init(uiColor: .brown).opacity(0.7)), .init("plane", .init(uiColor: .brown))]
case .planeAndChocolate:
[.init("chocolate", .init(uiColor: .black)), .init("plane", .init(uiColor: .brown))]
}
}
var scales: KeyValuePairs<String, Color> {
switch self {
case .matcha, .lemon:
[fravors[0].name: fravors[0].color]
default:
[fravors[0].name: fravors[0].color, fravors[1].name: fravors[1].color]
}
}
var fravorRatio: Int {
100 / fravors.count
}
}
解説
Chartsを利用したデザインで、ドーナッツメニューをイメージして作成した問題です。
キーポイント
WWDC23で発表されたpie chartsの機能を使っています。ご興味があれば、ぜひ以下のセッションもチェックしてみてください。 https://developer.apple.com/videos/play/wwdc2023/10037/
反省点
最終日で最後の問題でLTの時間と被っていたので、チャレンジできた人が少なかったかも。Chartsの問題は難易度が高いので、Chartsを知らない人でも回答しやすい問題にした方が良かったかも。
「SwiftUI Crossword Quiz」について詳細をお伝えできることを、心から嬉しく思います!昨年の「SwiftUI Preview Challenge」に続き、たくさんの方に参加してもらえて、ユニークな体験を提供できるブースイベントをこれからも続けていきたいと思ってます。この記事がみなさまの SwiftUI 学習の助けになればうれしいです!コメントやフィードバックもお待ちしております!最後まで読んでいただき、本当にありがとうございました!!
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。