ダイクストラ法で最短経路を求めてみる
ダイクストラ法という最短経路を求めるアルゴリズムをSwiftで試してみました。
参考にさせて頂いたのはこちらの記事です。
ダイクストラ法とは
ダイクストラ法(だいくすとらほう、英: Dijkstra’s algorithm)はグラフ理論における辺の重みが非負数の場合の単一始点最短経路問題を解くための最良優先探索によるアルゴリズムである。
Wikipediaによると上記のようなものになります。
今回はこれをSwiftで実装してみます。
実装
まずは画面上に点を設置します。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() (0...5).forEach { _ in let dot = UIView(frame: CGRect( x: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.width)))), y: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.height)))), width: 20, height: 20)) dot.backgroundColor = UIColor.darkGray dot.layer.cornerRadius = dot.frame.width / 2 view.addSubview(dot) } } }
次にStartとGoalを決めます。
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var dots: [UIView] = [] (0...5).forEach { _ in let dot = UIView(frame: CGRect( x: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.width)))), y: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.height)))), width: 20, height: 20)) dot.backgroundColor = UIColor.darkGray dot.layer.cornerRadius = dot.frame.width / 2 dots.append(dot) view.addSubview(dot) } let startLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) startLabel.text = "S" startLabel.textColor = UIColor.white startLabel.textAlignment = .center dots.first?.addSubview(startLabel) let goalLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) goalLabel.text = "G" goalLabel.textColor = UIColor.white goalLabel.textAlignment = .center dots.last?.addSubview(goalLabel) } }
続けて線同士を繋げて経路を作ろうと思います。
各点に他経路への参照を持たせるためにDotViewというクラスを作って各点をUIViewからDotViewにします。
下だと場合によっては循環参照が起こったり場合によってゴールまで着けない事もあるのですが、今回は「ダイクストラ法」の勉強という事で無視します。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var dots: [DotView] = [] // UIViewをDotViewに変更 (0...5).forEach { _ in let dot = DotView(frame: CGRect( // UIViewをDotViewに変更 x: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.width)))), y: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.height)))), width: 20, height: 20)) dot.backgroundColor = UIColor.darkGray dot.layer.cornerRadius = dot.frame.width / 2 dots.append(dot) view.addSubview(dot) } let startLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) startLabel.text = "S" startLabel.textColor = UIColor.white startLabel.textAlignment = .center dots.first?.addSubview(startLabel) let goalLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) goalLabel.text = "G" goalLabel.textColor = UIColor.white goalLabel.textAlignment = .center dots.last?.addSubview(goalLabel) // 以下今回追加 dots.forEach { dot in var otherDots = dots.filter { $0 != dot } dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) } } } // 以下が今回追加分 class DotView: UIView { var otherDots: [UIView] = [] }
上で参照を持たせたので、次は線を引きます。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var dots: [DotView] = [] (0...5).forEach { _ in let dot = DotView(frame: CGRect( x: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.width)))), y: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.height)))), width: 20, height: 20)) dot.backgroundColor = UIColor.darkGray dot.layer.cornerRadius = dot.frame.width / 2 dots.append(dot) view.addSubview(dot) } let startLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) startLabel.text = "S" startLabel.textColor = UIColor.white startLabel.textAlignment = .center dots.first?.addSubview(startLabel) let goalLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) goalLabel.text = "G" goalLabel.textColor = UIColor.white goalLabel.textAlignment = .center dots.last?.addSubview(goalLabel) dots.forEach { dot in var otherDots = dots.filter { $0 != dot } dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) } // 以下は今回追加 dots.forEach { dot in dot.otherDots.forEach { otherDot in let lineView = LineView(start: dot.frame.origin, end: otherDot.frame.origin) view.addSubview(lineView) view.sendSubview(toBack: lineView) } } } } class DotView: UIView { var otherDots: [DotView] = [] } // 以下は今回追加 class LineView: UIView { let start: CGPoint let end: CGPoint init(start: CGPoint, end: CGPoint) { self.start = start self.end = end super.init(frame: CGRect( x: ([start.x, end.x].min() ?? 0) + 10, y: ([start.y, end.y].min() ?? 0) + 10, width: abs(start.x - end.x), height: abs(start.y - end.y))) backgroundColor = UIColor.clear } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ rect: CGRect) { let path = UIBezierPath() path.move(to: CGPoint(x: start.x - frame.origin.x + 10, y: start.y - frame.origin.y + 10)) path.addLine(to: CGPoint(x: end.x - frame.origin.x + 10, y: end.y - frame.origin.y + 10)) path.lineWidth = 4.0 UIColor.darkGray.setStroke() path.stroke() } }
これで経路を作る事ができました。
ここからダイクストラ法を実装していきます。
まずはDotViewにtypeとscore(スタートからの距離)を追加して、viewDidLoadの中でtypeを設定します。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var dots: [DotView] = [] (0...5).forEach { _ in let dot = DotView(frame: CGRect( x: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.width)))), y: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.height)))), width: 20, height: 20)) dot.backgroundColor = UIColor.darkGray dot.layer.cornerRadius = dot.frame.width / 2 dots.append(dot) view.addSubview(dot) } let startLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) startLabel.text = "S" startLabel.textColor = UIColor.white startLabel.textAlignment = .center dots.first?.addSubview(startLabel) dots.first?.type = .start // 追加 let goalLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) goalLabel.text = "G" goalLabel.textColor = UIColor.white goalLabel.textAlignment = .center dots.last?.addSubview(goalLabel) dots.last?.type = .goal // 追加 dots.forEach { dot in var otherDots = dots.filter { $0 != dot } dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) } dots.forEach { dot in dot.otherDots.forEach { otherDot in let lineView = LineView(start: dot.frame.origin, end: otherDot.frame.origin) view.addSubview(lineView) view.sendSubview(toBack: lineView) } } } } // 今回修正 class DotView: UIView { enum DotType { case start case goal case normal } var otherDots: [DotView] = [] var score: CGFloat = 0 var type: DotType = .normal var routeDots: [DotView] = [] } // 省略
経路を作ったので、どれが最短経路かを計算します。
ViewControllerを以下のように修正します。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var dots: [DotView] = [] (0...5).forEach { _ in let dot = DotView(frame: CGRect( x: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.width)))), y: CGFloat(arc4random_uniform(UInt32(Int32(view.frame.height)))), width: 20, height: 20)) dot.backgroundColor = UIColor.darkGray dot.layer.cornerRadius = dot.frame.width / 2 dots.append(dot) view.addSubview(dot) } let startLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) startLabel.text = "S" startLabel.textColor = UIColor.white startLabel.textAlignment = .center dots.first?.addSubview(startLabel) dots.first?.type = .start let goalLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) goalLabel.text = "G" goalLabel.textColor = UIColor.white goalLabel.textAlignment = .center dots.last?.addSubview(goalLabel) dots.last?.type = .goal dots.forEach { dot in var otherDots = dots.filter { $0 != dot } dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) dot.otherDots.append(otherDots.remove(at: Int(arc4random_uniform(UInt32(otherDots.count))))) } dots.forEach { dot in dot.otherDots.forEach { otherDot in let lineView = LineView(start: dot.frame.origin, end: otherDot.frame.origin) view.addSubview(lineView) view.sendSubview(toBack: lineView) } } if let start = dots.first { var targetDots = [start] while targetDots.count != 0 { let targetDot = targetDots.remove(at: 0) targetDot.otherDots.forEach { let dx = targetDot.frame.origin.x - $0.frame.origin.x let dy = targetDot.frame.origin.y - $0.frame.origin.y if $0.score == 0 || targetDot.score + sqrt(dx*dx + dy*dy) < $0.score { $0.score = targetDot.score + sqrt(dx*dx + dy*dy) targetDots.append($0) $0.routeDots = targetDot.routeDots + [targetDot] } } } } if let last = dots.last, last.routeDots.count != 0 { last.routeDots.append(last) (0...(last.routeDots.count - 2)).forEach { let lineView = LineView( start: last.routeDots[$0].frame.origin, end: last.routeDots[$0 + 1].frame.origin, color: UIColor.red, lineWidth: 2) view.addSubview(lineView) } } } } class DotView: UIView { enum DotType { case start case goal case normal } var otherDots: [DotView] = [] var score: CGFloat = 0 var type: DotType = .normal var routeDots: [DotView] = [] } class LineView: UIView { let start: CGPoint let end: CGPoint let color: UIColor let lineWidth: CGFloat init(start: CGPoint, end: CGPoint, color: UIColor = UIColor.darkGray, lineWidth: CGFloat = 4) { self.start = start self.end = end self.color = color self.lineWidth = lineWidth super.init(frame: CGRect( x: ([start.x, end.x].min() ?? 0) + 10, y: ([start.y, end.y].min() ?? 0) + 10, width: abs(start.x - end.x), height: abs(start.y - end.y))) backgroundColor = UIColor.clear } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ rect: CGRect) { let path = UIBezierPath() path.lineWidth = lineWidth color.set() path.move(to: CGPoint(x: start.x - frame.origin.x + 10, y: start.y - frame.origin.y + 10)) path.addLine(to: CGPoint(x: end.x - frame.origin.x + 10, y: end.y - frame.origin.y + 10)) path.stroke() } }
実際の計算は下の部分です。
スタートから順番に点を辿り、その順路が最短なら更に探索、最短でなければそのルートは探索終了しています。
if let start = dots.first { var targetDots = [start] while targetDots.count != 0 { let targetDot = targetDots.remove(at: 0) targetDot.otherDots.forEach { let dx = targetDot.frame.origin.x - $0.frame.origin.x let dy = targetDot.frame.origin.y - $0.frame.origin.y if $0.score == 0 || targetDot.score + sqrt(dx*dx + dy*dy) < $0.score { $0.score = targetDot.score + sqrt(dx*dx + dy*dy) targetDots.append($0) $0.routeDots = targetDot.routeDots + [targetDot] } } } }
これを実行すると、以下のように最短経路を表示できた事が分かります。
rspec-retryでfeature specを安定させる
rspec-retry
という、失敗したテストを再実行するGemを使ってみました。
GitHub - NoRedInk/rspec-retry: retry randomly failing rspec example
まずはRailsのプロジェクトを作ってfeature specを書きます。
require 'rails_helper' feature 'test' do scenario 'ページが表示される' do visit users_path expect(current_path).to eq users_path end end
ここに一回目だけ失敗する処理を追加します。
require 'rails_helper' i = 0 feature 'test' do scenario 'ページが表示される' do i += 1 raise if i == 1 visit users_path expect(current_path).to eq users_path end end
これを実行するとエラーになります。
次はrspec-retry
でエラーが出たら何回かリトライするようにします。
まずはGemfileにrspec-retry
を追加します。
group :development, :test do gem 'rspec-retry' end
次はspec_helper.rbにrspec-retry
関連の設定を追加します。
require 'rspec/retry' RSpec.configure do |config| config.verbose_retry = true config.display_try_failure_messages = true end
最後にscenarioにretry回数を追加します。
require 'rails_helper' i = 0 feature 'test' do scenario 'ページが表示される', retry: 3 do i += 1 raise if i == 1 visit users_path expect(current_path).to eq users_path end end
実行すると、1回目で失敗したけど2回目で成功したことが分かります。
retryはscenarioではなくfeatureに付ける事もできます。
require 'rails_helper' i = 0 feature 'test', retry: 3 do scenario 'ページが表示される' do i += 1 raise if i == 1 visit users_path expect(current_path).to eq users_path end end
feature spec以外で使う事もできます。
require 'rails_helper' i = 0 RSpec.describe User, type: :model, retry: 3 do it do i += 1 raise if i == 1 expect(1).to eq 1 end end
全feature specを対象にしたい場合は、spec_helper.rbに以下に設定を追加します。
require 'rspec/retry' RSpec.configure do |config| config.verbose_retry = true config.display_try_failure_messages = true config.around :each, type: :feature do |ex| ex.run_with_retry retry: 3 end end
モンテカルロ法で円周率を求める
囲碁AIで有名なモンテカルロ法で円周率を計算できるようなので試してみました。
モンテカルロ法とは、Wikipediaによるとシミュレーションや数値計算を乱数を用いて行う手法の総称とのことです。
実装方法
計算の手順は下の通りです。
- 四角形とそれに内接する円を作成する
- 四角形の中に多数の点を配置する
- 円の内部にある点の数を円の面積だと仮定して円周率を計算する
まずは四角形と内接する円を作成します。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let v = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200)) v.backgroundColor = UIColor.darkGray view.addSubview(v) let circle = UIView(frame: v.bounds) circle.backgroundColor = UIColor.lightGray circle.layer.cornerRadius = circle.frame.width / 2 v.addSubview(circle) } }
これを実行すると、四角形と円が描画されます。
次は四角形の上に点を配置していきます。
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let v = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200)) v.backgroundColor = UIColor.darkGray view.addSubview(v) let circle = UIView(frame: v.bounds) circle.backgroundColor = UIColor.lightGray circle.layer.cornerRadius = circle.frame.width / 2 v.addSubview(circle) // ここから今回追加 var dots: [UIView] = [] (0...40000).forEach { _ in let x = CGFloat(arc4random_uniform(201)) let y = CGFloat(arc4random_uniform(201)) let dot = UIView(frame: CGRect(x: x, y: y, width: 1, height: 1)) dot.backgroundColor = UIColor.white v.addSubview(dot) dots.append(dot) } } }
実行すると、点が配置されているのが分かります。
次に円の中の点の数を計算します。
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let v = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200)) v.backgroundColor = UIColor.darkGray view.addSubview(v) let circle = UIView(frame: v.bounds) circle.backgroundColor = UIColor.lightGray circle.layer.cornerRadius = circle.frame.width / 2 v.addSubview(circle) var dots: [UIView] = [] (0...40000).forEach { _ in let x = CGFloat(arc4random_uniform(201)) let y = CGFloat(arc4random_uniform(201)) let dot = UIView(frame: CGRect(x: x, y: y, width: 1, height: 1)) dot.backgroundColor = UIColor.white v.addSubview(dot) dots.append(dot) } // ここから今回追加 let count = dots.filter { let dx = $0.frame.origin.x - circle.center.x let dy = $0.frame.origin.y - circle.center.y let radius = circle.frame.size.width / 2 return dx*dx + dy*dy < radius*radius }.count print(count) // → 74 } }
最後に円の中の点(円の面積)を元に円周率を求めます。
結果は、3.1111と3.14に近い数字となりました。
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let v = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200)) v.backgroundColor = UIColor.darkGray view.addSubview(v) let circle = UIView(frame: v.bounds) circle.backgroundColor = UIColor.lightGray circle.layer.cornerRadius = circle.frame.width / 2 v.addSubview(circle) var dots: [UIView] = [] (0...40000).forEach { _ in let x = CGFloat(arc4random_uniform(201)) let y = CGFloat(arc4random_uniform(201)) let dot = UIView(frame: CGRect(x: x, y: y, width: 1, height: 1)) dot.backgroundColor = UIColor.white v.addSubview(dot) dots.append(dot) } let count = dots.filter { let dx = $0.frame.origin.x - circle.center.x let dy = $0.frame.origin.y - circle.center.y let radius = circle.frame.size.width / 2 return dx*dx + dy*dy < radius*radius }.count let radius = circle.frame.size.width / 2 print(CGFloat(count) / radius / radius) // 3.1111 } }
【GameplayKit】GKRuleSystemで複雑な条件を管理する
GKRuleSystemという複数の条件を管理する機能を使ってみました。
使い方は下の通りです。
- GKRuleSystemにGKRuleを追加する
- GKRuleSystemにパラメータをセットする
- GKRuleSystemの評価メソッド(evaluate)を呼ぶ
- 評価の結果を取得する
コードは下の通りです。
評価の結果は、GKRuleの引数のgradeの数字が返ってきます。
system.add
がルールの追加、system.state["value1"] = 0
がパラメータのセット、system.evaluate()
が評価メソッドの呼び出し、system.grade(forFact: NSString(string: "fact1"))
が結果の取得になります。
let system = GKRuleSystem() system.add([ GKRule(predicate: NSPredicate(format: "$value1 = 0"), assertingFact: NSString(string: "fact1"), grade: 0.9), ]) system.state["value1"] = 0 system.reset() system.evaluate() print(system.grade(forFact: NSString(string: "fact1"))) // → 0.9
条件式がfalseの場合、結果は0になります。
let system = GKRuleSystem() system.add([ GKRule(predicate: NSPredicate(format: "$value1 = 0"), assertingFact: NSString(string: "fact1"), grade: 0.9), ]) system.state["value1"] = 1 // → 今回は1をセット system.reset() system.evaluate() print(system.grade(forFact: NSString(string: "fact1"))) // → 0.0
GKRuleは複数渡す事ができます。
複数の条件を満たす場合、結果は全ての条件の和になります。
let system = GKRuleSystem() system.add([ GKRule(predicate: NSPredicate(format: "$value1 = 0"), assertingFact: NSString(string: "fact1"), grade: 0.1), GKRule(predicate: NSPredicate(format: "$value2 = 1"), assertingFact: NSString(string: "fact1"), grade: 0.2), ]) system.state["value1"] = 0 system.state["value2"] = 1 system.reset() system.evaluate() print(system.grade(forFact: NSString(string: "fact1"))) // → 0.3
しかし結果は1.0以上にはならないので注意が必要です。
和が1.0以上の場合は、1.0が返ってきます。
let system = GKRuleSystem() system.add([ GKRule(predicate: NSPredicate(format: "$value1 = 0"), assertingFact: NSString(string: "fact1"), grade: 1.1), GKRule(predicate: NSPredicate(format: "$value2 = 1"), assertingFact: NSString(string: "fact1"), grade: 2.2), ]) system.state["value1"] = 0 system.state["value2"] = 1 system.reset() system.evaluate() print(system.grade(forFact: NSString(string: "fact1"))) // → 1.0
GKRuleの初期化のassertingFactをretractingFactに変えると、条件を満たした時にgradeを減算するようになります。
これも和の時と同じように0.0以下にはなりません。
let system = GKRuleSystem() system.add([ GKRule(predicate: NSPredicate(format: "$value1 = 0"), assertingFact: NSString(string: "fact1"), grade: 0.5), GKRule(predicate: NSPredicate(format: "$value2 = 1"), retractingFact: NSString(string: "fact1"), grade: 0.2), ]) system.state["value1"] = 0 system.state["value2"] = 1 system.reset() system.evaluate() print(system.grade(forFact: NSString(string: "fact1"))) // → 0.3
GKRuleはNSPredicateではなくブロックで評価をする事もできます。
GKRule(blockPredicate: { system in return true }, action: { system in print("条件を満たした") })
GKRuleのサブクラスを作って、そこに評価式を書くこともできます。
複雑な条件の時はこれを使うのも良さそうです。
class MyRule: GKRule { override func evaluatePredicate(in system: GKRuleSystem) -> Bool { return true } override func performAction(in system: GKRuleSystem) { print("条件を満たした") } }
【Swift】ベジェ曲線を自前で描いてみる
ベジェ曲線って良く聞くんですが、イマイチ理解できてなかったので自前で描いてみました。
具体的にはベジェ曲線の座標を自分で計算して描画をしてみました。
ベジェ曲線の座標の求め方
ベジェ曲線の座標は制御点を使って求められます。
今回は下のように制御点が3つの場合のベジェ曲線を求めます。
まずはP1とP2、P2とP3の2点間をつなぎます。
次にP1-P2間で少しだけP1から離れた点P4とP2-P3で少しだけP2から離れたP5を決めて、それらをつなぎます。
そしてP4-P5間で少しだけP4から離れた点P6がベジェ曲線の座標になります。
段々とP4・P5をP2・P3に近づけて行きつつ、それぞれのP6を算出します。
ここで算出したP6を全てつなげればベジェ曲線になります。
今回はこれをSwiftで実装してみようと思います。
ベジェ曲線をSwiftで描画
今回はこの3つの制御点を使ったベジェ曲線を求めます。
現状のコードは下の通りです。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let myView = MyView(frame: view.bounds) myView.backgroundColor = UIColor.white view.addSubview(myView) } } class MyView: UIView { let point1 = CGPoint(x: 100, y: 200) let point2 = CGPoint(x: 200, y: 100) let point3 = CGPoint(x: 300, y: 200) override func draw(_ rect: CGRect) { UIColor.darkGray.setFill() UIBezierPath(roundedRect: CGRect(origin: point1, size: CGSize(width: 5, height: 5)), cornerRadius: 2).fill() UIBezierPath(roundedRect: CGRect(origin: point2, size: CGSize(width: 5, height: 5)), cornerRadius: 2).fill() UIBezierPath(roundedRect: CGRect(origin: point3, size: CGSize(width: 5, height: 5)), cornerRadius: 2).fill() } }
先程描いたように、point1・point2・point3を元に、point6を求めてそれを繋いでいきます。
class MyView: UIView { let point1 = CGPoint(x: 100, y: 200) let point2 = CGPoint(x: 200, y: 100) let point3 = CGPoint(x: 300, y: 200) override func draw(_ rect: CGRect) { let count = 100 let path = UIBezierPath() path.move(to: point1) (0...count).forEach { let point4 = CGPoint( x: point1.x + (point2.x - point1.x) * CGFloat($0) / CGFloat(count), y: point1.y + (point2.y - point1.y) * CGFloat($0) / CGFloat(count) ) let point5 = CGPoint( x: point2.x + (point3.x - point2.x) * CGFloat($0) / CGFloat(count), y: point2.y + (point3.y - point2.y) * CGFloat($0) / CGFloat(count) ) let point6 = CGPoint( x: point4.x + (point5.x - point4.x) * CGFloat($0) / CGFloat(count), y: point4.y + (point5.y - point4.y) * CGFloat($0) / CGFloat(count) ) path.addLine(to: point6) } path.addLine(to: point3) path.lineWidth = 5.0 UIColor.brown.setStroke() path.stroke() } }
これを実行すると以下のようになります。
無事にきれいな曲線を引くことができました。
【Swift3】UIViewのdrawの中で線や文字や画像を描画する
UIViewのdrawメソッド中では線・文字・矩形など様々なものを描画できます。
今回はそれらの描画を試してみました。
検証用コードは下の通りです。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let myView = MyView(frame: view.bounds) myView.backgroundColor = UIColor.white view.addSubview(myView) } } class MyView: UIView { override func draw(_ rect: CGRect) { } }
線を引く
線の描画は以下の通りです。
線の太さや色を変える事ができます。
class MyView: UIView { override func draw(_ rect: CGRect) { let path = UIBezierPath() path.move(to: CGPoint(x: 100, y: 100)) path.addLine(to: CGPoint(x: 200, y: 200)) path.lineWidth = 5.0 // 線の太さ UIColor.brown.setStroke() // 色をセット path.stroke() } }
実行結果は以下の通りです。
addLineを追加すれば複雑な線も作る事ができます。
class MyView: UIView { override func draw(_ rect: CGRect) { let path = UIBezierPath() path.move(to: CGPoint(x: 100, y: 100)) path.addLine(to: CGPoint(x: 200, y: 200)) path.addLine(to: CGPoint(x: 300, y: 100)) // 今回追加 path.lineWidth = 5.0 // 線の太さ UIColor.brown.setStroke() // 色をセット path.stroke() } }
曲線を描画する
addCurveメソッドを使う事でベジェ曲線を描画する事ができます。
class MyView: UIView { override func draw(_ rect: CGRect) { let path = UIBezierPath() path.move(to: CGPoint(x: 100, y: 100)) path.addCurve(to: CGPoint(x: 200, y: 200), controlPoint1: CGPoint(x: 150, y: 100), controlPoint2: CGPoint(x: 200, y: 150)) path.lineWidth = 5.0 // 線の太さ UIColor.brown.setStroke() // 色をセット path.stroke() } }
曲線の描画はaddArcでも行う事ができます。
class MyView: UIView { override func draw(_ rect: CGRect) { let path = UIBezierPath() path.move(to: CGPoint(x: 100, y: 100)) path.addArc(withCenter: CGPoint(x: 200, y: 100), radius: 100, startAngle: 180 * CGFloat.pi/180, endAngle: 90 * CGFloat.pi/180, clockwise: false) path.lineWidth = 5.0 // 線の太さ UIColor.brown.setStroke() // 色をセット path.stroke() } }
四角形の描画
四角形の描画は以下のように行います。
class MyView: UIView { override func draw(_ rect: CGRect) { let path = UIBezierPath(roundedRect: CGRect(x: 100, y: 100, width: 100, height: 100), cornerRadius: 10) UIColor.darkGray.setFill() // 色をセット path.fill() } }
addLineを使って描画する事もできます。
class MyView: UIView { override func draw(_ rect: CGRect) { let path = UIBezierPath() path.move(to: CGPoint(x: 100, y: 100)) path.addLine(to: CGPoint(x: 200, y: 100)) path.addLine(to: CGPoint(x: 200, y: 200)) path.addLine(to: CGPoint(x: 100, y: 200)) path.addLine(to: CGPoint(x: 100, y: 100)) UIColor.darkGray.setFill() // 色をセット path.fill() } }
文字の描画
文字の描画は以下のように行います。
第2引数でフォントや文字色の指定もできます。
class MyView: UIView { override func draw(_ rect: CGRect) { "MyText".draw(at: CGPoint(x: 100, y: 100), withAttributes: [ NSForegroundColorAttributeName : UIColor.blue, NSFontAttributeName : UIFont.systemFont(ofSize: 50), ]) } }
画像の描画
画像もテキスト同様にdrawメソッドを使います。
class MyView: UIView { override func draw(_ rect: CGRect) { UIImage(named: "sample")?.draw(at: CGPoint(x: 100, y: 100)) } }
WKWebViewのキャッシュなどをクリアする
個人で出しているブラウザアプリの容量がかなりの大きさになっていたので調査しました。
キャッシュ等の削除方法は下の通りです。
これでアプリ容量が500MB → 60MBまで減りました。
WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: Date(timeIntervalSince1970: 0), completionHandler: {})
「キャッシュ・cookieだけ」のように削除対象を絞りたい場合は第一引数に削除したい対象をセットします。
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler: {})
キーとして使えるものは以下の通りです。
public let WKWebsiteDataTypeDiskCache: String public let WKWebsiteDataTypeMemoryCache: String public let WKWebsiteDataTypeOfflineWebApplicationCache: String public let WKWebsiteDataTypeCookies: String public let WKWebsiteDataTypeSessionStorage: String public let WKWebsiteDataTypeLocalStorage: String public let WKWebsiteDataTypeWebSQLDatabases: String public let WKWebsiteDataTypeIndexedDBDatabases: String