- 投稿日:2020-02-20T20:32:05+09:00
Neumorphismの電卓を作ってみる
Neumorphismの電卓
Neumorphismのデザインが素敵だったので,練習で仲間と一緒に電卓を作ってみました!
とりあえず動くようになったので公開しようと思います。
ほとんど彼につくってもらいましたが...この電卓は,主に
NeumorLabel
とNeumorButton
の2つから作られています。Neumorphismとは
参考
ボタンなどの要素が凹凸で表現されている,新しいスキューモーフィックデザインのことみたいです。
NeumorLabel
コード全体
swift/NeumorLabel.swiftimport Foundation import UIKit class NeumorLabel: UILabel { override init(frame: CGRect) { super.init(frame: frame) label() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) label() } private let params = Params() private let topAndBottomLayer = CAGradientLayer(), leftAndRightLayer = CAGradientLayer(), calcResultLabel = UILabel() private func label() { putTopAndBottom() putLeftAndRight() addSubview(calcResultLabel) } private func putTopAndBottom() { topAndBottomLayer.cornerRadius = params.LABEL_CORNER_RADIUS topAndBottomLayer.frame = self.bounds topAndBottomLayer.colors = [ params.BACKGROUND_COLOR.darker().cgColor, params.NORMAL_BUTTON_BGCOLOR.cgColor, params.NORMAL_BUTTON_BGCOLOR.cgColor, params.BACKGROUND_COLOR.brighter().cgColor ] topAndBottomLayer.locations = [ 0, 0.15, 0.85, 1 ] topAndBottomLayer.opacity = 1 layer.insertSublayer(topAndBottomLayer, at: 0) } private func putLeftAndRight() { leftAndRightLayer.cornerRadius = params.LABEL_CORNER_RADIUS leftAndRightLayer.frame = self.bounds leftAndRightLayer.colors = [ params.BACKGROUND_COLOR.darker().cgColor, params.NORMAL_BUTTON_BGCOLOR.cgColor, params.NORMAL_BUTTON_BGCOLOR.cgColor, params.BACKGROUND_COLOR.brighter().cgColor ] leftAndRightLayer.locations = [ 0, 0.05, 0.95, 1 ] leftAndRightLayer.startPoint = CGPoint(x: 0, y: 0) leftAndRightLayer.endPoint = CGPoint(x: 1, y: 0) leftAndRightLayer.opacity = 0.5 layer.insertSublayer(leftAndRightLayer, at: 1) } func putUILabel(text: String) { calcResultLabel.text = text calcResultLabel.frame = CGRect( x: 15, y: 0, width: self.bounds.width * 0.9, height: self.bounds.height ) calcResultLabel.textAlignment = .right calcResultLabel.font = UIFont(name: "Roboto-Bold", size: 30) } }コードのざっくりとした解説
storyboardで指定するためのクラス
NeumorLabel.swift
を作成しました。
CAGradientLayer
をただ上につけるだけだと文字が隠れちゃって表示されない現象が起きてしまったので,putUILabel()
でさらに上にUILabel
を載せられるようにしています。(ViewControllerからputUILabel()
は参照しています)
Layerは影と光を表現するために使っています。左ななめ上から光があたってる感じになっているます。それぞれ白色と黒色で再現しています。NeumorSquareButton
練習で円いボタンも作成していたので,名前にわざと"Square"がついています。
コード全体
swift/NeumorSquareButton.swiftimport Foundation import UIKit let FONT_SIZE = 16 class NeumorSquareButton: UIButton { override init(frame: CGRect){ super.init(frame: frame) button() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) button() } private let params = Params() let highlightLayer = CALayer(), shadowLayer = CALayer(), textLayer = CATextLayer() func button() { setTitleColor(UIColor.clear, for: .normal) textLayer.string = currentTitle textLayer.foregroundColor = UIColor.gray.cgColor textLayer.contentsScale = UIScreen.main.scale textLayer.fontSize = CGFloat(FONT_SIZE) textLayer.alignmentMode = .center [highlightLayer, shadowLayer, textLayer].forEach { $0.masksToBounds = false $0.frame = layer.bounds } textLayer.frame = CGRect( x: CGFloat(0), y: self.bounds.height/2 - CGFloat(FONT_SIZE/2), width: self.bounds.width, height: CGFloat(FONT_SIZE) ) putHighlight() putShadow() layer.addSublayer(shadowLayer) layer.addSublayer(highlightLayer) layer.addSublayer(textLayer) } private func putHighlight() { highlightLayer.backgroundColor = params.NORMAL_BUTTON_BGCOLOR.cgColor highlightLayer.shadowColor = params.BACKGROUND_COLOR.brighter().cgColor highlightLayer.cornerRadius = layer.frame.size.height * 0.15 highlightLayer.shadowOpacity = 1 highlightLayer.shadowOffset = CGSize(width: -6, height: -6) highlightLayer.shadowRadius = 10 } private func putShadow() { shadowLayer.backgroundColor = UIColor.white.cgColor shadowLayer.shadowColor = UIColor.black.cgColor shadowLayer.cornerRadius = layer.frame.size.height * 0.15 shadowLayer.shadowOpacity = 0.15 shadowLayer.shadowOffset = CGSize(width: 6, height: 6) shadowLayer.shadowRadius = 10 } func buttonPush() { highlightLayer.shadowOpacity = 0 shadowLayer.shadowOpacity = 0 let frame = CGRect(x: 0, y: 0, width: layer.frame.size.width, height: layer.frame.size.height) let gradColors = [ params.BACKGROUND_COLOR.darker().cgColor, params.NORMAL_BUTTON_BGCOLOR.cgColor, params.NORMAL_BUTTON_BGCOLOR.cgColor, params.BACKGROUND_COLOR.brighter().cgColor ] let gradLocations = [0, 0.15, 0.85, 1] let shadowOpacity: Float = 0.5 let cornerRadius = layer.frame.size.height * 0.15 let virticalGradLayer = CAGradientLayer() virticalGradLayer.opacity = shadowOpacity virticalGradLayer.frame = frame virticalGradLayer.colors = gradColors virticalGradLayer.locations = gradLocations as [NSNumber] virticalGradLayer.cornerRadius = cornerRadius let horizonGradLayer = CAGradientLayer() horizonGradLayer.opacity = shadowOpacity horizonGradLayer.frame = frame horizonGradLayer.startPoint = CGPoint(x: 0, y: 0) horizonGradLayer.endPoint = CGPoint(x: 1, y: 0) horizonGradLayer.colors = gradColors horizonGradLayer.locations = gradLocations as [NSNumber] horizonGradLayer.cornerRadius = cornerRadius layer.addSublayer(horizonGradLayer) layer.addSublayer(virticalGradLayer) layer.addSublayer(textLayer) } }コードのざっくりとした解説
押されたときのアニメーションを地道につけていってます...
押されたときのアニメーション↓Labelのときと同じように影と光を配置しています。
おわりに
ただ電卓を公開したかっただけだったので,一応githubのリポジトリを貼っておきます。
https://github.com/Papillon6814/neumor-calculator/blob/master/README.md
- 投稿日:2020-02-20T16:18:47+09:00
Face IDを使ってみた開発メモ(Objective-C編)
事前準備
1.プロジェクトから「info」→「Custom iOS Target Properties」に
「Privacy - Face ID Usage Description 」を追加する。
内容はFaceIDの使用を許可するダイアログメッセージに使用される。
2.プロジェクトにLocalAuthetication.frameworkを追加する。
「Build Phases」→「Link Binary With Libraries」→「+」ボタンから追加
3.使用するクラスにimportを追加
import "LocalAuthentication/LocalAuthentication.h"使用するメソッドの説明
- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError * _Nullable *)error;動かしている端末が指定の生体認証に対応しているかチェックするメソッド。
指定する「LAPolicy」には以下のようなパターンがある。
LAPolicy 内容 LAPolicyDeviceOwnerAuthenticationWithBiometrics 生体認証 LAPolicyDeviceOwnerAuthenticationWithWatch AppleWatchによる認証 LAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch 生体認証かAppleWatchのいずれかによる認証 LAPolicyDeviceOwnerAuthentication 端末で認証として設定しているいずれかの認証 今回は生体認証が使用できるかどうかをチェックするので
「LAPolicyDeviceOwnerAuthenticationWithBiometrics」を使用する。
「error」は「LAError」で返ってくる。詳細は参考から。- (void)evaluatePolicy:(LAPolicy)policy localizedReason:(NSString *)localizedReason reply:(void (^)(BOOL success, NSError *error))reply;指定した生体認証を行うメソッド。
「localizedReason」には実際に生体認証を行う際のダイアログメッセージとして利用される。
生体認証の成否は「success」でtrue/falseで返ってくる。
こちらも「error」には「LAError」が返ってくる。詳細は参考から。サンプル
LAContext *context = [[LAContext alloc] init]; NSError *authError = nil; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) { [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"今回はテストでFaceIDを使用します" reply:^(BOOL success, NSError *error) { if (success) { NSLog(@"ログインに成功しました"); } else { NSLog(@"ログイン出来ませんでした。"); } }]; } else { NSLog(@"FaceIDに対応していない端末でした"); }参考
公式ドキュメント
https://developer.apple.com/documentation/localauthentication/lacontext?language=objc
- 投稿日:2020-02-20T12:02:41+09:00
iOSとAndroidのダークモード検証を楽にしよう
ダークモードの検証忘れて文字が見えない!
はい、これ良くやらかしてませんか?
最近 iOSもAndroidもダークモードが追加され、デフォルトからを背景画像や文字色に使用すると、OSのダークモード設定に合わせて自動でカラーの反転がされますよね。それで、背景は白固定にしてるのに、文字色だけデフォルトにしていて文字が見えない!ということが起きます。開発時・テスト時のダークモード検証を楽にしよう
ダークモードのテストするとき、毎度設定画面を開くのって面倒ですよね。少ないアクションで相互に切り替えれると便利です。実はiOSのAndroidもコントロールセンター(画面上からしたスワイプして出てくる画面)に下記のようにダークモードの切り替えボタンを追加できます!これすごく便利だと思いませんか?
iOS
Android
設定方法
以下、各OSでの設定方法をまとめておきます。OSのバージョンや、Androidだと端末が異なると設定方法も変わるので、環境が違う場合は自身の環境に合わせて調べて見てくださいね。
iOS (iPhone XR / 13.3.1)
設定アプリTop コントロールセンター カスタマイズ Android (Pixel 4 XL / Andriod 10)
クイック設定パネル カスタマイズ
- 投稿日:2020-02-20T11:06:31+09:00
NSURLErrorDomainのコード一覧
NSURLErrorDomain のコード一覧を置いておきますね。ログでコードでてきてもすぐに結びつかないので...。
name value NSURLErrorUnknown -1 NSURLErrorCancelled -999 NSURLErrorBadURL -1000 NSURLErrorTimedOut -1001 NSURLErrorUnsupportedURL -1002 NSURLErrorCannotFindHost -1003 NSURLErrorCannotConnectToHost -1004 NSURLErrorNetworkConnectionLost -1005 NSURLErrorDNSLookupFailed -1006 NSURLErrorHTTPTooManyRedirects -1007 NSURLErrorResourceUnavailable -1008 NSURLErrorNotConnectedToInternet -1009 NSURLErrorRedirectToNonExistentLocation -1010 NSURLErrorBadServerResponse -1011 NSURLErrorUserCancelledAuthentication -1012 NSURLErrorUserAuthenticationRequired -1013 NSURLErrorZeroByteResource -1014 NSURLErrorCannotDecodeRawData -1015 NSURLErrorCannotDecodeContentData -1016 NSURLErrorCannotParseResponse -1017 NSURLErrorAppTransportSecurityRequiresSecureConnection -1022 NSURLErrorFileDoesNotExist -1100 NSURLErrorFileIsDirectory -1101 NSURLErrorNoPermissionsToReadFile -1102 NSURLErrorDataLengthExceedsMaximum -1103 NSURLErrorFileOutsideSafeArea -1104 NSURLErrorSecureConnectionFailed -1200 NSURLErrorServerCertificateHasBadDate -1201 NSURLErrorServerCertificateUntrusted -1202 NSURLErrorServerCertificateHasUnknownRoot -1203 NSURLErrorServerCertificateNotYetValid -1204 NSURLErrorClientCertificateRejected -1205 NSURLErrorClientCertificateRequired -1206 NSURLErrorCannotLoadFromNetwork -2000 NSURLErrorCannotCreateFile -3000 NSURLErrorCannotOpenFile -3001 NSURLErrorCannotCloseFile -3002 NSURLErrorCannotWriteToFile -3003 NSURLErrorCannotRemoveFile -3004 NSURLErrorCannotMoveFile -3005 NSURLErrorDownloadDecodingFailedMidStream -3006 NSURLErrorDownloadDecodingFailedToComplete -3007 NSURLErrorInternationalRoamingOff -1018 NSURLErrorCallIsActive -1019 NSURLErrorDataNotAllowed -1020 NSURLErrorRequestBodyStreamExhausted -1021 NSURLErrorBackgroundSessionRequiresSharedContainer -995 NSURLErrorBackgroundSessionInUseByAnotherProcess -996 NSURLErrorBackgroundSessionWasDisconnected -997
- 投稿日:2020-02-20T10:17:31+09:00
【iOS】UIButtonにボーダーラインを引けるようにする
色と太さのほか、各辺の開始点・終了点のオフセットを指定できます。
コード
import UIKit class BorderedButton: UIButton { private var topBorder: CALayer = CALayer() private var bottomBorder: CALayer = CALayer() private var leftBorder: CALayer = CALayer() private var rightBorder: CALayer = CALayer() private var layers: [CALayer] = [] private var widths: [CGFloat] = [0, 0, 0, 0] private var startOffsets: [CGFloat] = [0, 0, 0, 0] private var endOffsets: [CGFloat] = [0, 0, 0, 0] override init(frame: CGRect) { super.init(frame: frame) setUp() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setUp() } private func setUp() { layers = [topBorder, bottomBorder, leftBorder, rightBorder] layers.forEach { $0.backgroundColor = UIColor(red: 221/255, green: 221/255, blue: 221/255, alpha: 0.92).cgColor self.layer.addSublayer($0) } } func setBorderColor(_ color: UIColor) { layers.forEach { $0.backgroundColor = color.cgColor } } func setTopBorder(width: CGFloat, startOffset: CGFloat, endOffset: CGFloat) { widths[0] = width startOffsets[0] = startOffset endOffsets[0] = endOffset } func setBottomBorder(width: CGFloat, startOffset: CGFloat, endOffset: CGFloat) { widths[1] = width startOffsets[1] = startOffset endOffsets[1] = endOffset } func setLeftBorder(width: CGFloat, startOffset: CGFloat, endOffset: CGFloat) { widths[2] = width startOffsets[2] = startOffset endOffsets[2] = endOffset } func setRightBorder(width: CGFloat, startOffset: CGFloat, endOffset: CGFloat) { widths[3] = width startOffsets[3] = startOffset endOffsets[3] = endOffset } override func layoutSubviews() { super.layoutSubviews() layoutBorderFrame() } private func layoutBorderFrame() { topBorder.frame = CGRect(x: startOffsets[0], y: 0, width: self.frame.width - (startOffsets[0] + endOffsets[0]), height: widths[0]) bottomBorder.frame = CGRect(x: startOffsets[1], y: self.frame.height - widths[1], width: self.frame.width - (startOffsets[1] + endOffsets[1]), height: widths[1]) leftBorder.frame = CGRect(x: 0, y: startOffsets[2], width: widths[2], height: self.frame.height - (startOffsets[2] + endOffsets[2])) rightBorder.frame = CGRect(x: self.frame.width - widths[3], y: startOffsets[3], width: widths[3], height: self.frame.height - (startOffsets[3] + endOffsets[3])) } }使い方
let button = BorderedButton() button.setTopBorder(width: 4, startOffset: 240, endOffset: 0) button.setRightBorder(width: 36, startOffset: 0, endOffset: 0) button.setLeftBorder(width: 36, startOffset: 0, endOffset: 0) button.setBottomBorder(width: 4, startOffset: 0, endOffset: 240) button.setBorderColor(UIColor.orange) button.setTitle("Hello Bordered Button", for: .normal) button.setTitleColor(.black, for: .normal)
- 投稿日:2020-02-20T00:44:57+09:00
【ARKit】childNode(withName:recursively:)でThread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
はじめに
こちらを参考にARKitで3Dモデルを表示しようとしたところ、「 Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value」のエラーが出たので、その対処を備忘録として。
実行環境
- Xcode 11.2.1
- MagicaVoxel 0.99.4
- Mixamo
コード
上記リンクのサンプルはボタンで使うscnを切り替えていたのですが、簡単にテストするために変数を「swing」固定としました。また SceneKit Catalog の名前も変更(art.scnassets)してあります。
ViewController.swiftvar selectedItem: String? = "swing" //(中略) let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn")エラーになった箇所
以下のfunctionの childNode(withName:recursively:) で 「 Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value」のエラーが出ました。
addItemfunc addItem(hitTestResult: ARHitTestResult) { if let selectedItem = self.selectedItem { // アセットのより、シーンを作成 let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn") // ノード作成 let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))! // 現実世界の座標を取得 let transform = hitTestResult.worldTransform let thirdColumn = transform.columns.3 // アイテムの配置 node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z) self.mainSceneView.scene.rootNode.addChildNode(node) } }原因
SCNScene.childNode(withName:recursively:) は、SCNScene から withNname で指定した名前のノードを取得するメソッドですが、swing.scnに "swing" というノードが存在しないためでした。
対処
swing.scnを開いて、Scene graphパネルの左下の「+」ボタンでノードを追加して「swing」に名前を変更して、元々あったノードを「swing」ノードの配下にドラッグして移動します。
3Dオブジェクトのサイズ調整
3Dオブジェクトのサイズが大きすぎたので、サイズ変更のコードを追加してあります。
node.scale = SCNVector3(0.05, 0.05, 0.05)
addItem/// アイテム配置メソッド func addItem(hitTestResult: ARHitTestResult) { if let selectedItem = self.selectedItem { // アセットのより、シーンを作成 let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn") // ノード作成 let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))! // 現実世界の座標を取得 let transform = hitTestResult.worldTransform let thirdColumn = transform.columns.3 // アイテムの配置 node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z) // アイテムのサイズを変更 node.scale = SCNVector3(0.05, 0.05, 0.05) self.mainSceneView.scene.rootNode.addChildNode(node) } }ちなみに MagicaVoxelで作ったときのサイズは、21 21 21 でした。
この辺の適正サイズや,sncファイルのscale値で変更した方がいいのかは、現時点ではちょっとわからず。。。
まとめ
前提の知識(3Dモデルとか)が乏しいのでいろいろ躓いています。他の3Dアプリで作ったモデルはちゃんとノード名がつくんですかね。。。
MagicaVoexlについてはこちらを参考にしました。やはり公開することを考えると、オリジナルのモデルが必要になるので、簡単でいいです。
MagicaVoexlにアニメーションをつけるのには、Maximoを使いました。こちらもモデルをアップロードしてアニメーション選ぶだけなので、これまた簡単です。こちらを参考にしました(モデルをMagicaVoexlで作る場合は、Blender部分はすっ飛ばして大丈夫です)。