- 投稿日:2020-01-15T23:10:22+09:00
Siri に Slackbot が投稿した AWS 請求額を読み上げる仕事をさせる
はじめに
iPhone を使い始めて10年弱、 Siri が全く働いていないことに気付きました。
スマートスピーカーが流行っている昨今、自分の Siri にももっと活躍して欲しいので Slackbot が投稿した AWS 請求額を読み上げてもらうことにしました。Slackに投稿した内容を読み上げてくれました。
では、作り方諸々を紹介していきます。構成
それぞれの役割を紹介していきます。
- Siri
- 音声呼び出しによるショートカットアプリの実行
- Slack に投稿された AWS 請求額の読み上げ
- ショートカットアプリ
- SlackAPI を使用して投稿
- SlackAPI を使用して最新の投稿を JSON で取得
- 取得された JSON 情報から必要な内容を抽出
- Slackbot
- Lambda の呼び出し
- Lambda
- AWS 請求額の取得
処理内容
今回はショートカットアプリに重点を置いて説明していきます。
Lambda と Slackbot についてはそれぞれ以下の記事にて詳細を記載しているので参照してください。
Lambda:(Python)AWSの請求金額を取得する
Slackbot:echobotを作成し、Slackに通知するそれでは、ショートカットを作成していきます。
まず、ショートカット名を決めます。
Siri にショートカット名を呼びかけると登録しているショートカットを実行してくれます。
なので今回は『今月の料金は』というショートカット名にします。
(『AWSの料金は』としたかったのですが Siri が AWS の部分を理解してくれませんでした...)続いて、各処理で使用する変数を定義していきます。
- token:SlackAPI で使用する token (今回は Legacy tokens を使用しています。)
- channel_nm:SlackAPI で投稿するチャンネル名
- channel_id:SlackAPI で最新の投稿を取得する対象のチャンネルID
- text:Slack に投稿する文章
変数が設定できたら、 Slack 投稿用 API の URL を作成し、実行します。
API を実行すると、 AWS 請求額取得 Lambda が実行されます。結果がSlackに投稿されるまで時間がかかるので待機させます。
待機後、 Slack の最新投稿取得APIを実行していきます。
投稿用 API の時と同様に URL を作成し、APIを実行します。
最新投稿取得 API を実行すると以下のような JSON が返却されます。{"messages": [{ "bot_id":"XXXXXXXX", "ts":"1579095866.000200", "attachments": [{ "color":"36a64f", "id":1, "fallback":" ・AWS Cost Explorer: 1.04 USD\n ・Tax: 0.10 USD", "pretext":"01月01日から01月14日の請求額は、1.14 USDです。", "text":" ・AWS Cost Explorer: 1.04 USD\n ・Tax: 0.10 USD" }], "type":"message", "subtype":"bot_message", "text":"" }], "has_more":true, "ok":true, "channel_actions_ts":null, "channel_actions_count":0}ここから pretext の値を変数に追加していきます。
これで Siri に読み上げてもらう内容が取得できました。
最後に読み上げる文章をテキストに起こし、読み上げのアクションを設定します。
完成です。
Siri に向かって『 Hey Siri! 』と話しかけましょう!おわりに
これで Siri が一つ仕事を覚えてくれました。
この構成を使えばもっといろいろな事ができると思います。
今回は Slack + Lambda をショートカットアプリから呼び出しましたが、 アプリの Pythonista3 から Python を実行しても面白い事ができると思います。
- 投稿日:2020-01-15T22:53:58+09:00
【swift5】自作電卓アプリを作ってみた
まずは全体像から
GIFがこちら
全体のデザインがこちら
全体のコード
githubはこちらから
https://github.com/sventouz/calculatorimport UIKit class ViewController: UIViewController { var numberOnScreen:Int = 0 var previousNumber:Int = 0 var performingMath = false var operation = 0 @IBOutlet weak var label: UILabel! @IBAction func numbers(_ sender: UIButton) { if performingMath == true { label.text = String(sender.tag-1) numberOnScreen = Int(label.text!)! performingMath = false } else { label.text = label.text! + String(sender.tag-1) numberOnScreen = Int(label.text!)! } } @IBAction func buttons(_ sender: UIButton) { if label.text != "" && sender.tag != 11 && sender.tag != 16{ previousNumber = Int(label.text!)! if sender.tag == 12{ // ÷ label.text = "÷"; } else if sender.tag == 13{ // × label.text = "×"; } else if sender.tag == 14{ // - label.text = "-"; } else if sender.tag == 15{ // + label.text = "+"; } operation = sender.tag performingMath = true; } else if sender.tag == 16 // = が押された時の処理 { if operation == 12{ label.text = String(previousNumber / numberOnScreen) } else if operation == 13{ label.text = String(previousNumber * numberOnScreen) } else if operation == 14{ label.text = String(previousNumber - numberOnScreen) } else if operation == 15{ label.text = String(previousNumber + numberOnScreen) } } else if sender.tag == 11{ // C が押された時の処理 label.text = "" previousNumber = 0; numberOnScreen = 0; operation = 0; } } override func viewDidLoad() { super.viewDidLoad() } }コードを説明していきます
コードの接続
まずコードでは表示されていませんが、0〜9の数字を
@IBAction func numbers(_ sender: UIButton) { }のなかにドラッグアンドドロップします。
こちらも同様に+, -, ÷, × をドラッグアンドドロップ。
@IBAction func buttons(_ sender: UIButton) { }IBアクションではありません。connect actionです。
tag追加
tagを追加していきます。
0には1を
1には2をつけていき9に10がつけばOK
次は
Cに11
÷に12
と続き
=が16になれば完璧です。
続き
@IBAction func numbers(_ sender: UIButton) { // この中が数字をクリックしたときに動く場所 }@IBAction func buttons(_ sender: UIButton) { // この中が数字以外の四則関数をクリックしたときに動く場所 }あとは全体のコードを見ながら各自コードを解読していってください。
感想
電卓なんか簡単でしょ?って思っていたのですが難しかったです。笑
もっとシンプルなのを作っていき最終的には複雑なものを作っていければいいと思う。
その過程ではアウトプットを忘れずに!
参考
https://blog.codecamp.jp/iphone-app-develope-original-calculator
- 投稿日:2020-01-15T22:53:58+09:00
【swift5】自作電卓アプリを作ってみよう(完全版)
まずは全体像から
GIFがこちら
全体のデザインがこちら
全体のコード
githubはこちらから
https://github.com/sventouz/calculatorimport UIKit class ViewController: UIViewController { var numberOnScreen:Int = 0 var previousNumber:Int = 0 var performingMath = false var operation = 0 @IBOutlet weak var label: UILabel! @IBAction func numbers(_ sender: UIButton) { if performingMath == true { label.text = String(sender.tag-1) numberOnScreen = Int(label.text!)! performingMath = false } else { label.text = label.text! + String(sender.tag-1) numberOnScreen = Int(label.text!)! } } @IBAction func buttons(_ sender: UIButton) { if label.text != "" && sender.tag != 11 && sender.tag != 16{ previousNumber = Int(label.text!)! if sender.tag == 12{ // ÷ label.text = "÷"; } else if sender.tag == 13{ // × label.text = "×"; } else if sender.tag == 14{ // - label.text = "-"; } else if sender.tag == 15{ // + label.text = "+"; } operation = sender.tag performingMath = true; } else if sender.tag == 16 // = が押された時の処理 { if operation == 12{ label.text = String(previousNumber / numberOnScreen) } else if operation == 13{ label.text = String(previousNumber * numberOnScreen) } else if operation == 14{ label.text = String(previousNumber - numberOnScreen) } else if operation == 15{ label.text = String(previousNumber + numberOnScreen) } } else if sender.tag == 11{ // C が押された時の処理 label.text = "" previousNumber = 0; numberOnScreen = 0; operation = 0; } } override func viewDidLoad() { super.viewDidLoad() } }コードを説明していきます
コードの接続
まずコードでは表示されていませんが、0〜9の数字を
@IBAction func numbers(_ sender: UIButton) { }のなかにドラッグアンドドロップします。
こちらも同様に+, -, ÷, × をドラッグアンドドロップ。
@IBAction func buttons(_ sender: UIButton) { }IBアクションではありません。connect actionです。
tag追加
tagを追加していきます。
0には1を
1には2をつけていき9に10がつけばOK
次は
Cに11
÷に12
と続き
=が16になれば完璧です。
続き
@IBAction func numbers(_ sender: UIButton) { // この中が数字をクリックしたときに動く場所 }@IBAction func buttons(_ sender: UIButton) { // この中が数字以外の四則関数をクリックしたときに動く場所 }あとは全体のコードを見ながら各自コードを解読していってください。
感想
電卓なんか簡単でしょ?って思っていたのですが難しかったです。笑
もっとシンプルなのを作っていき最終的には複雑なものを作っていければいいと思う。
その過程ではアウトプットを忘れずに!
参考
https://blog.codecamp.jp/iphone-app-develope-original-calculator
- 投稿日:2020-01-15T13:25:10+09:00
Universal LinksのTeam ID指定が他と違う場合がある
iOSのUniversal Linksを設定していたが、シミュレータでは正しく挙動するものの実機では一切動かないということが起こった。
- App Search API Validation Tool では
Error no apps with domain entitlements
と出ていたが、これは関係がないという記事が多く見られた。- branch のバリデータ では正しく設定されていると出ていた
アプリ側でちゃんとEntitlementsの値がセットされているかを確認するために、codesignコマンドでEntitlementsを確認してみると…
$ codesign -d --entitlements :- MyApp.app<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>application-identifier</key> <string>BBBBBBBBBB.net.iseteki.myapp</string> <key>aps-environment</key> <string>development</string> <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> <key>com.apple.developer.associated-domains</key> <array> <string>applinks:myapp.example.com</string> <string>activitycontinuation:myapp.example.com</string> </array> <key>com.apple.developer.team-identifier</key> <string>AAAAAAAAAA</string> <key>get-task-allow</key> <true/> </dict> </plist>…おわかりいただけたであろうか?
application-identifierのprefixとteam-identifierが一致してない!
昔から作られているアプリはApp ID Prefixが異なる場合がある
昔、iOS Developer Portal(当時名称)でApp IDを作る際にApp ID Prefixを新規に生成することができた。現在はTeam ID固定となっていてこの設定をすることはできないようになっているが、昔から存在するApp IDの場合はこのBundle ID Prefixの指定が残っている。
App IDに指定されているApp ID PrefixはApp IDの設定画面で確認できる。
App ID Prefixの末に (Team ID) と書かれていない場合は、Team IDとは異なるApp ID Prefixになっている。
- 投稿日:2020-01-15T13:25:10+09:00
Universal LinksのPrefix指定が他と違う場合がある
iOSのUniversal Linksを設定していたが、シミュレータでは正しく挙動するものの実機では一切動かないということが起こった。
- apple-site-association には、
[Team ID].[Bundle ID]
の形式でapplinksを記載していた- App Search API Validation Tool では
Error no apps with domain entitlements
と出ていたが、これは関係がないという記事が多く見られた。- branch のバリデータ では正しく設定されていると出ていた
アプリ側でちゃんとEntitlementsの値がセットされているかを確認するために、codesignコマンドでEntitlementsを確認してみると…
$ codesign -d --entitlements :- MyApp.app<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>application-identifier</key> <string>BBBBBBBBBB.net.iseteki.myapp</string> <key>aps-environment</key> <string>development</string> <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> <key>com.apple.developer.associated-domains</key> <array> <string>applinks:myapp.example.com</string> <string>activitycontinuation:myapp.example.com</string> </array> <key>com.apple.developer.team-identifier</key> <string>AAAAAAAAAA</string> <key>get-task-allow</key> <true/> </dict> </plist>…おわかりいただけたであろうか?
application-identifierのprefixとteam-identifierが一致してない!
昔から作られているアプリはApp ID Prefixが異なる場合がある
昔、iOS Dev CenterでApp IDを作る際にApp ID Prefixを新規に生成することができた。現在はTeam ID固定となっていてこの設定をすることはできないようになっているが、昔から存在するApp IDの場合はこのBundle ID Prefixの指定が残っている。
App IDに指定されているApp ID PrefixはApp IDの設定画面で確認できる。
App ID Prefixの末に (Team ID) と書かれていない場合は、Team IDとは異なるApp ID Prefixになっている。
apple-site-association に指定するBundle IdentifierのPrefixをここに表示されているApp ID Prefixに修正することで問題なく動作するようになった。
- 投稿日:2020-01-15T13:25:10+09:00
Universal LinksのApp ID PrefixがTeam IDでない場合がある
iOSのUniversal Linksを設定していたが、シミュレータでは正しく挙動するものの実機では一切動かないということが起こった。
- apple-site-association には、
[Team ID].[Bundle ID]
の形式でapplinksを記載していた- App Search API Validation Tool では
Error no apps with domain entitlements
と出ていたが、これは関係がないという記事が多く見られた。- branch のバリデータ では正しく設定されていると出ていた
アプリ側でちゃんとEntitlementsの値がセットされているかを確認するために、codesignコマンドでEntitlementsを確認してみると…
$ codesign -d --entitlements :- MyApp.app<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>application-identifier</key> <string>BBBBBBBBBB.net.iseteki.myapp</string> <key>aps-environment</key> <string>development</string> <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> <key>com.apple.developer.associated-domains</key> <array> <string>applinks:myapp.example.com</string> <string>activitycontinuation:myapp.example.com</string> </array> <key>com.apple.developer.team-identifier</key> <string>AAAAAAAAAA</string> <key>get-task-allow</key> <true/> </dict> </plist>…おわかりいただけたであろうか?
application-identifierのprefixとteam-identifierが一致してない!
昔から作られているアプリはApp ID Prefixが異なる場合がある
昔、iOS Dev CenterでApp IDを作る際にApp ID Prefixを新規に生成することができた。現在はTeam ID固定となっていてこの設定をすることはできないようになっているが、昔から存在するApp IDの場合はこのBundle ID Prefixの指定が残っている。
App IDに指定されているApp ID PrefixはApp IDの設定画面で確認できる。
App ID Prefixの末に (Team ID) と書かれていない場合は、Team IDとは異なるApp ID Prefixになっている。
apple-site-association に指定するBundle IdentifierのPrefixをここに表示されているApp ID Prefixに修正することで問題なく動作するようになった。
- 投稿日:2020-01-15T07:54:46+09:00
NeumorphismなUIをSwiftUIで作れるライブラリ、Neumorphismicを作ってみた
先日Neumorphism: 令和時代のスキューモーフィズムを読みました。
これが流行る頃にはSwiftUIも使えるようになってるだろう…ということでSwiftUIでNeumorphismのライブラリを作ってみました。
Switch
などは作っていません。今後時間があれば作っていきたいですが、まだ形がコレと定まってもいないので難しそうですね。そもそも流行るのかもわかりませんし。完成形
ModifierでShadowを実装する
Modifierについてはこの記事でひと通りわかると思います。簡単にいうと
.font
とか.frame
とかをまとめてView
に適合できるやつです。Button
などの場合はButtonStyle
などが使えればいいのですが、Appleさんが公開していないので諦めます。
SwiftUIでもshodowは1つしか追加できません。なのでZStack
でView
を2つ重ねてそれぞれに.shadow
をつける必要があります。struct ConvexModifier: ViewModifier { let lightShadowColor: Color let darkShadowColor: Color func body(content: Content) -> some View { ZStack { content .shadow(color: darkShadowColor, radius: 16, x: 9, y: 9) content .shadow(color: lightShadowColor, radius: 16, x: -9, y: -9) } } }しかし、これだけでは影の色をいちいち入力する必要があります。
色をEnvironmentで伝搬する
SwiftUIでは
@Environment
を使うことでその子View
全てに値を伝えることができます。詳しくはこの記事を読んでください。
そしてこれは自作することもできます。Neumorphismでは基本的にView
に1色しか使わないためこれが非常に有効です。さらに、ConvexModifier
もbaseColor
を基準に影の色を決めればよくなります。自作する方法はこちらをご覧ください。struct BaseColorKey: EnvironmentKey { static let defaultValue: Color = .gray } extension EnvironmentValues { var baseColor: Color { get { self[BaseColorKey.self] } set { self[BaseColorKey.self] = newValue } } }さて、色を変換したいわけだけど…
パッと見、SwiftUIの
Color
からはrgbやhueや取得できそうにありません。が、優しきAppleさんは.description
を用意してくれていました。#C1D2EBFF
のようにカラーコードを返してくれます。ということでカラーコードからColorを生成(このリンクではUIColor)できるようにし、RGBとHSL、RGBとHSBの変換コードを用意します。
Color
から色情報を取れるとわかったので、早速lighterColor
を実装します。neumorphismPrimary(value:)
は下に出てくるFloatingTabView
のラベルなどで使われています。func getHSLA() -> (h: Double, s: Double, l: Double, a: Double) { let string = String(self.description.dropFirst()) let v = Int(string, radix: 16) ?? 0 let r = Double(v / Int(powf(256, 3)) % 256) / 255 let g = Double(v / Int(powf(256, 2)) % 256) / 255 let b = Double(v / Int(powf(256, 1)) % 256) / 255 let a = Double(v / Int(powf(256, 0)) % 256) / 255 let (h, s, l) = ColorTransformer.rgbToHsl(r: r, g: g, b: b) return (h, s, l, a) } func lighter(value: Double) -> Color { let (h, s, l, a) = getHSLA() let hsb = ColorTransformer.hslToHsb(h: h, s: s, l: l + value) return Color(hue: hsb.h, saturation: hsb.s, brightness: hsb.b, opacity: a) } func darker(value: Double) -> Color { let (h, s, l, a) = getHSLA() let hsb = ColorTransformer.hslToHsb(h: h, s: s, l: l - value) return Color(hue: hsb.h, saturation: hsb.s, brightness: hsb.b, opacity: a) } func primary(value: Double) -> Color { let (_, _, l, _) = getHSLA() return (l > 0.5) ? darker(value: value) : lighter(value: value) }struct ColorExtension_Previews: PreviewProvider { static var previews: some View { let color = Color(hex: "C1D2EB") return Group { ColorPreview(color) ColorPreview(color.lighter(value: 0.12)) ColorPreview(color.darker(value: 0.18)) } .previewLayout(.fixed(width: 200, height: 100)) } }ConvexModifierを完成させる
材料は揃ったので合わせてみましょう。
struct ConvexModifier: ViewModifier { @Environment(\.baseColor) var baseColor: Color func body(content: Content) -> some View { ZStack { content .shadow(color: baseColor.darkerColor(value: 0.18), radius: 16, x: 9, y: 9) content .shadow(color: baseColor.lighterColor(value: 0.12), radius: 16, x: -9, y: -9) } } }struct ConvexModifier_Previews: PreviewProvider { static var previews: some View { ZStack { Color(hex: "C1D2EB") .edgesIgnoringSafeArea(.all) Circle() .fill(Color(hex: "C1D2EB")) .modifier(ConvexModifier()) .frame(width: 300, height: 300) } .environment(\.baseColor, Color(hex: "C1D2EB")) } }
.environment
でbaseColor
を伝えるの忘れないようにしましょう。
いい感じですね!
environment
を使ったので当然struct ConvexModifier_Previews: PreviewProvider { static var previews: some View { ZStack { Color(hex: "C1D2EB") .edgesIgnoringSafeArea(.all) Circle() .fill(Color(hex: "C1D2EB")) .frame(width: 300, height: 300) .modifier(ConvexModifier()) // `environment`で影の色を変える .environment(\.baseColor, Color.red) } .environment(\.baseColor, Color(hex: "C1D2EB")) } }とできるだろうと思っていたのですができませんでした。
.red
の時は黒い影が出てきて、その他の場合は影がなくなりました。Color(hex:)
を使ったら行けたのでよくわかりません。Highlight時に表示を変えられるButtonも作ってみた
ハイライト時にNeumorphismではどうするのが正解なんだろうと考えるためにとりあえず作ってみたのですが、標準の
Button
でもいい気がします。個人的に標準Button
は ハイライト時に色が薄すぎる気がするのでこれを使ってます。押下時にボタンを小さくしたいときなどに使ってみてください。struct HighlightableButton<Label>: View where Label: View { private let action: () -> Void private let label: (Bool) -> Label public init( action: @escaping () -> Void, label: @escaping (Bool) -> Label ) { self.action = action self.label = label } @State private var isHighlighted = false public var body: some View { label(isHighlighted) .animation(.easeOut(duration: 0.05)) .gesture(DragGesture(minimumDistance: 0) .onChanged { _ in withAnimation { self.isHighlighted = true } } .onEnded { _ in self.action() withAnimation { self.isHighlighted = false } } ) } }struct ConvexModifier_HighlightableButton_ForPreviews: View { @State var isSelected = false var body: some View { HighlightableButton(action: { self.isSelected.toggle() }) { isH in Image(systemName: self.isSelected ? "house.fill" : "house") .resizable() .aspectRatio(contentMode: .fit) .frame(width: isH ? 54 : 60) .foregroundColor(Color(hex: "C1D2EB").darker(value: 0.18)) .background( Circle() .fill(Color(hex: "C1D2EB")) .frame(width: isH ? 90 : 100, height: isH ? 90 : 100) .modifier(ConvexModifier()) ) .opacity(isH ? 0.6 : 1) } } }
.modifier(ConvexModifier())
はButton
の中に書いてください。外に書くときちんと適用されません。ForPreviews
みたいな名前になっているのは、PreviewProvider
の中には@State
が置けないからです。
FloatingTabViewも作ってみた
@touyouもタブを作ってることだし作るかーみたいな感じで作ってみました。
ViewBuilder
を読み解こうとまでは思わなかったので、標準のTabView
のような綺麗さはありませんが、まあ使えるのではないでしょうか。タブの数は4つまでにしてみました。その他を実装する気力はなかったので切り捨てています。あとで多分何とかします。
使い方はこんな感じです。struct FloatingTabView_ForPreviews: View { enum Season: String, CaseIterable { case spring, summer, fall, winter var color: Color { switch self { case .spring: return .pink case .summer: return .blue case .fall: return .orange case .winter: return .white } } } @State var season: Season = .spring var body: some View { FloatingTabView(selection: $season, labelText: { s in s.rawValue }, labelImage: { _ in Image(systemName: "camera") }) { s in s.color.edgesIgnoringSafeArea(.all) } } }例を作るのが面倒なのが目に見えますね…。
labelImage
なんてカメラだけですし。この例では色を変えていますが、Neumorphismでは色を変えるのはご法度なので注意。(もちろん局所的にアクセントとして使うのはOKです)
ああ、影の色が汚い…。まあ色変えるとこうなるよっていう悪い例と思ってください。
ちなみにGeometryReader
のせいかLive PreviewにしていないとTabが下に落ちてしまいました。SwiftUIは7不思議どころじゃありません。そこら辺に穴が一杯です。全く理由がわからず何となくLive Previewにしてみたところ正しいことがわかりました。恐ろしや。凹も作りたかったけど
凹凸どちらも作りたかったのですが、凹の方はいい案が思い浮かばず、凸だけになってしまいました。適合したい
aView
よりひとまわり大きいbView
を作って、そこからaView
の大きさを切り抜いて、aView
の上にbView
を2つ置いて影をつければ行けそうだなとは思ったんですが、くり抜く方法がわかりませんでした。Path
を使えば何とかなりそうですが、View
の形を取る方法もないですし…。
あと、SwiftUI製ですし、全プラットフォームに対応させたかったのですが、macOSにSF Symbolsがなかったり、tvOSにDragGesture
がなかったりと面倒になってやめました() まあそこまで大変そうでもないのでいつかします。使う上での諸注意
HighlightableButton
のところでも書きましたが、Button
で使う際は.modifier(ConvexModifier())
をButton
の中に書いてください。そうしないときれいに作れません。
またBinding
系のコンポーネントは2つ同時にViewに存在すると使えなくなるようです。TextField("C1D2EB", text: $model.userInput) .foregroundColor(baseColor.nmPrimary(value: 1)) .padding(5) .background( RoundedRectangle(cornerRadius: 5, style: .continuous) .fill(baseColor) .modifier(NMConvexModifier(radius: 9)) )のように
background
に設定したView
に.modifier(ConvexModifier())
してください。View
の量的にもこちらの方がパフォーマンスもいいです。初OSS & 初Qiita?
SwiftUIなのでマルチプラットフォームに対応のOSSにしたかったわけですが、iOSのみとは作り方が違うようで。WWDCを参考に作らせていただきました。
Qiitaも初投稿ですが、そろそろ開発から離れて受験勉強しないと浪人する未来しか見えないので当分記事を書くことはないでしょう。1年後に戻って来れるように頑張ります。いいねとスターつけてくださると嬉しいです!
- 投稿日:2020-01-15T01:40:42+09:00
Flutterで有名なアプリのUIを再現する(Twitter編①)
この記事を見るとここまでできます
今回は初回なので、まだまだ形になっていません。
今回の記事のポイント(要点だけ知りたい方はここだけ読んでください)
画像を円形にして表示する
Twitterでは、左上に表示されている自身の画像や、投稿に表示される投稿者の画像が円形に加工されて表示されています。
この部分の実装は下記のようにしています。Container( margin: EdgeInsets.all(8.0), decoration: BoxDecoration( shape: BoxShape.circle, image: DecorationImage( fit: BoxFit.fill, image: NetworkImage(iconImgUrl), ), ), )ContainerのdecorationプロパティにBoxDecorationを指定しています。
BoxDecorationのshapeプロパティでBoxShape.circleを指定することで円形に加工されます。
また、画像もDecorationImageのfitプロパティでBoxFit.fillを指定することで円形全体に画像が表示されます。BottomNavigationBarに4つ以上のボタンを配置する
表示環境にもよるかもしれませんが、私の環境の場合だとBottomNavigationBarに4つ以上のボタンを配置すると表示位置が均等にならず、背景色の指定が無効化されてしまいましたが、最終的に下記のようにすることで解決しました。
BottomNavigationBar( type: BottomNavigationBarType.fixed, backgroundColor: Color.fromRGBO(30, 40, 54, 1.0), showSelectedLabels: false, showUnselectedLabels: false, items: <BottomNavigationBarItem>[ _menuButton(Icons.home), _menuButton(Icons.search), _menuButton(Icons.notifications_none), _menuButton(Icons.mail_outline), ], )上記のようにtypeプロパティにBottomNavigationBarType.fixedを指定する事で解決しました。
typeはデフォルトではBottomNavigationBarType.shiftingが指定されており、固定化されていないようでした。ここから本題です
このシリーズのゴール
UI周りの実装方法を学習するには有名なアプリを真似て作ればゴールも明確だし、完成した時の満足感も高いだろう、という事で今回はFlutterでTwitterアプリを再現しようと思います。
あくまでUI周りの実装を学習する過程を纏めた記事なので、Twitterクライアントを作るとか、機能を作りこむといったところまではせず、見た目と動作がそっくりになるところまで進めようと思います。
今のところ、5,6回くらいに分けて投稿する予定ですが、実装を進めながらの投稿になりますので、ハマったポイントがあった場合は番外記事も投稿するかもしれません。
続きが気になる方はフォローをお願いします。
今回のゴール
今回は初回なので、上下のメニュー部分を実装します。
コードと解説
アプリのメインは下記のようにしました。
main.dartimport 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Twitter', home: MainPage(), ); } }ルートページとしてMainPageをインスタンス化していますので、次はMainPageのコードです。
少し長いのでメソッドごとに4つに分けて解説します。
実際のコードはMainPageクラス内に全て実装しています。main.dartclass MainPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: _twitterAppBar( 'https://d1f5hsy4d47upe.cloudfront.net/79/791ba2a1c3245ae92712c393fe2b6408_w.jpg'), body: Container( color: Color.fromRGBO(30, 40, 54, 1.0), ), bottomNavigationBar: _twitterBottomBar(), ); } }Twitterアプリのメイン画面は大きく分けると3つのセクションで構成されています。
- アプリケーションバー:画面上部のアカウントアイコン、Twitterのアイコン、星アイコンが表示されている部分
- 本文:画面中央のTwitterの投稿が表示される部分
- メニュー:画面下部の各画面への遷移するためのメニュー部分
上記コードのbuildメソッドではScaffoldを返しています。
Scaffoldは、マテリアルデザイン用のウィジェットで、マテリアルデザインの各要素がプロパティとして定義されています。
その中で今回は、
- appBarプロパティ:アプリケーションバーにあたる部分です。_twitterAppBarメソッドで中身を実装しています。
- bodyプロパティ:本文にあたる部分です。今回は背景色を付けているだけですので、説明は割愛します。
- bottomNavigationBarプロパティ:メニューにあたる部分です。_twitterBottomBarメソッドで中身を実装しています。
続いて、_twitterAppBarメソッドのコードです。
main.dartWidget _twitterAppBar(String iconImgUrl) { return AppBar( backgroundColor: Color.fromRGBO(30, 40, 54, 1.0), leading: Container( margin: EdgeInsets.all(8.0), decoration: BoxDecoration( shape: BoxShape.circle, image: DecorationImage( fit: BoxFit.fill, image: NetworkImage(iconImgUrl), ), ), ), centerTitle: true, title: Text('Trial'), actions: <Widget>[ Container( margin: EdgeInsets.all(8.0), child: Icon( Icons.star_border, color: Color.fromRGBO(76, 158, 235, 1.0), size: 45, ), ), ], ); }このメソッドではAppBarを返しています。
ポイントは下記の3点です。
- leadingプロパティ
アプリケーションバーの左側に表示されるウィジェットを指定する部分です。
Twitterでは、ログインユーザーのアイコン画像が円状に表示されているので、BoxDecorationを使って画像を円状に変形させています。
- titleプロパティ
アプリケーションバーの中央に表示されるウィジェットを指定する部分です。
Twitterでは、Twitterのアイコンが表示されますが勝手に拝借するわけにもいかないので、この部分はテキストを表示してあります。
centerTitleプロパティをtrueにしておかないと、Androidでは左寄せで表示されます。
- actionsプロパティ
アプリケーションバーの右側に表示されるウィジェットを指定する部分です。
Twitterでは、表示順ルール変更用の星マークが表示されているので、代替となるアイコンを表示しています。次は、_twitterBottomBarメソッドです。
main.dartWidget _twitterBottomBar() { return Container( margin: EdgeInsets.only(top: 0.1), child: BottomNavigationBar( type: BottomNavigationBarType.fixed, backgroundColor: Color.fromRGBO(30, 40, 54, 1.0), showSelectedLabels: false, showUnselectedLabels: false, items: <BottomNavigationBarItem>[ _menuButton(Icons.home), _menuButton(Icons.search), _menuButton(Icons.notifications_none), _menuButton(Icons.mail_outline), ], ), ); }このメソッドでは、BottomNavigationBarでメニューを実装しています。
(Containerで囲われていますが、これはbody部分との境界をつけるためのマージンを指定するためです)ポイントは、typeプロパティに指定したBottomNavigationBarType.fixedの部分で、これを指定することでボタンの位置が固定化されます。
また、showSelectedLabelsプロパティとshowUnselectedLabelsプロパティでボタンのラベルを非表示にしています。ボタンの実装は_menuButtonメソッドに実装しました。
main.dartBottomNavigationBarItem _menuButton(IconData icon) { return BottomNavigationBarItem( icon: Icon( icon, color: Color.fromRGBO(139, 152, 164, 1.0), ), activeIcon: Icon( icon, color: Color.fromRGBO(76, 158, 235, 1.0), ), title: Text(''), ); }非選択状態のアイコンをiconプロパティに、選択状態にアイコンをactiveIconプロパティに指定したBottomNavigationBarItemを返しています。
次回
自身とフォロワーの投稿が表示される本文を実装予定です。
あとがき
文字色や背景色を各箇所で指定していますが、themeを使ってまとめた方がよさそうですね。
ただ、themeで各プロパティにColor.fromRGBOを使うとエラーになってしまう部分が解決できず・・・。
このあたりも解決次第投稿予定です。