はじめに
今回はサイバーエージェント主催の平成最後のハッカソンで開発した「平成振り返りデバイス」のiOSアプリ版を最近発表されたSwiftUIで開発したぜ的な記事になります。
平成振り返りデバイスとは
以下の写真にあるデバイスを体に身に付けて物理的に振り返るとWebサイトに平成のニュースやtwitterのバズったツイートが届くサービスになっています。デモ動画はこちらにあります。
平成振り返りアプリ
平成振り返りデバイスは手元にデバイスが無いと遊べないため、同じようなアプリを作ってみんなに遊んでもらいたい!と思い開発しました。以下のようにiPhoneのジャイロセンサで振り向きを検知したら平成のニュースやツイートが届きます。
開発環境
- iOS13
- Xcode11
- SwiftUI
View
簡単にソースコードの紹介をします。
CardView
ニュースを表示させるカードのソースコードです。これをList表示します。
import SwiftUI
struct CardView : View {
var newsModel: NewsModel!
var body: some View {
VStack {
HStack {
Spacer()
Image(uiImage: self.getImageByUrl(url: newsModel.img))
.resizable()
.frame(width: 300, height: 200)
.aspectRatio(contentMode: .fit)
Spacer()
}
Text(newsModel!.title)
.font(.title)
Text("date: \(newsModel.date)")
.font(.subheadline)
.color(.gray)
Text("ジャンル: \(newsModel.genre)")
.font(.subheadline)
.color(.gray)
PresentationButton(
Text("READ MORE")
.color(.blue)
, destination: ReadMoreView(url: newsModel.url))
}
.padding(.top)
}
func getImageByUrl(url: String) -> UIImage{
let url = URL(string: url)
do {
let data = try Data(contentsOf: url!)
return UIImage(data: data)!
} catch let err {
print("Error : \(err.localizedDescription)")
}
return UIImage()
}
}
ReadMoreView
ニュースのリンクを表示させるWebViewです。SwiftUIのチュートリアルでMapViewを作っていた感じと同じです。
import SwiftUI
import WebKit
struct ReadMoreView : UIViewRepresentable {
var url: String!
func makeUIView(context: Context) -> WKWebView {
WKWebView(frame: .zero)
}
func updateUIView(_ view: WKWebView, context: Context) {
let urlNS = NSURL(string: url)
let req = NSURLRequest(url:urlNS! as URL)
view.load(req as URLRequest)
}
}
ViewModel
ViewModelでは振り向きの検知や、ニュースを取得するAPIを叩いたりしてListに表示するデータを保持します。ジャイロセンサで角速度[rad/s]を取得しそれを積分して90度を超えた時にニュースを取得するようにしています。
import SwiftUI
import Combine
import CoreMotion
import Alamofire
final class LookBackViewModel: BindableObject {
let didChange = PassthroughSubject<LookBackViewModel, Never>()
let motionManager = CMMotionManager()
let url = [
"twitter": "https://script.google.com/macros/s/AKfycbyhR8HyQKcf2b9wRUmsCm-6D_EK1zFlJzIpPIhrBuRd49FVFtpT/exec",
"heisei": "https://script.google.com/macros/s/AKfycbxc-TKyZ8Lp-9Ed05et_wIGw55RLGBGwhNSY2lb2z9iQdy1wLs/exec"]
var gyro_x: Double! = 0
var gyro_y: Double! = 0
var gyro_z: Double! = 0
var degree:Double! = 0
var imageName = "heisei" {
didSet {
didChange.send(self)
}
}
var newsList: [NewsModel] = [] {
didSet {
didChange.send(self)
}
}
init() {
self.motionCapture()
}
func request(url: String) {
Alamofire.request(url,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: nil)
.response { response in
guard let data = response.data else { return }
let newsModel: NewsModel = try! JSONDecoder().decode(NewsModel.self, from: data)
self.newsList.insert(newsModel, at: 0)
}
}
func motionCapture() {
motionManager.deviceMotionUpdateInterval = 0.1
motionManager.startDeviceMotionUpdates( to: OperationQueue.current!, withHandler:{
deviceManager, error in
let gyro: CMRotationRate = deviceManager!.rotationRate
self.gyro_x = gyro.x
self.gyro_y = gyro.y
self.gyro_z = gyro.z
self.loop(angular: gyro.y)
})
}
func loop(angular: Double){
self.degree += angular * 0.1 * 57.2
if self.degree >= 90{
self.imageName = "left"
self.request(url: self.url["twitter"]!)
self.degree = 0
} else if -self.degree >= 90{
self.imageName = "right"
self.request(url: self.url["heisei"]!)
self.degree = 0
}
}
}
Model
SwiftのCodableを使ってAPIで取得したニュースのJSONを以下で定義した型に変換します。
import SwiftUI
import Foundation
struct NewsModel: Hashable, Codable {
let title: String
let url: String
let date: String
let img: String
let genre: String
}
おわりに
まだチュートリアルで勉強した程度のUI実装なので汚いですがSwiftUIを使って1つのアプリが開発できた事はすごく自信になりました。これからはよりUIのデザインを洗練しアプリの動作もサクサク動くようにしていきたいです。今回の実装はGitHubにアップしています。アドバイスなどありましたらコメントお願いします!!!