30
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwiftやiOSの便利だけど忘れそうな小ネタ集

Last updated at Posted at 2019-01-09

俺得なので誰の参考にもならない
swift書いてて毎度書き方を忘れたり仕様を忘れたり存在自体を忘れたりしてるやつらのまとめ

SceneDelegate.swiftから起動

SceneDelegate.swift

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

  var window: UIWindow?

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let scene = (scene as? UIWindowScene) else {
      return
    }
    let window = UIWindow(windowScene: scene)
    self.window = window

    let vc = UIViewController()
    vc.view.backgroundColor = .systemRed
    window.rootViewController = vc

    window.makeKeyAndVisible()
  }

  func sceneDidDisconnect(_ scene: UIScene) {
  }

  func sceneDidBecomeActive(_ scene: UIScene) {
  }

  func sceneWillResignActive(_ scene: UIScene) {
  }

  func sceneWillEnterForeground(_ scene: UIScene) {
  }

  func sceneDidEnterBackground(_ scene: UIScene) {
  }
}

#実行時間計測

##実装

final class Benchmark {
  static func measure(_ message: String = "", block: () -> Void) {
    let startTime = Date()
    block()
    let elapsed = Date().timeIntervalSince(startTime) as Double
    let formatedElapsed = String(format: "%.3f", elapsed)
    print("Benchmark: \(message), 実行時間: \(formatedElapsed)(s)")
  }
}

##使い方
計測したい処理をクロージャに入れる

Benchmark.measure("append") {
  var appendList: [String] = []
  for v in 1...1000 {
    appendList.append(v.description)
  }
}
Benchmark.measure("map") {
  let _ = Array(1...1000).map { $0.description }
}

#Xcode_previews

UIView,UIViewControllerと連携

extension

View_ViewController+Representable.swift
#if canImport(SwiftUI) && DEBUG
import SwiftUI

@available(iOS 13.0, *)
enum PreviewDeviceType: String {
    case se1 = "iPhone SE (1st generation)"
    case eight = "iPhone 8"
    case x = "iPhone X"
    case se2 = "iPhone SE (2nd generation)"
    case eleven = "iPhone 11"
    case elevenProMax = "iPhone 11 Pro Max"

    var device: PreviewDevice { PreviewDevice(rawValue: self.rawValue) }

    var displayName: String {
        switch self {
        case .se1: return "SE1"
        case .eight: return "8"
        case .x: return "X"
        case .se2: return "SE2"
        case .eleven: return "11"
        case .elevenProMax: return "11 Pro Max"
        }
    }
}

@available(iOS 13.0, *)
extension UIView {
    private struct Preview: UIViewRepresentable {
        let view: UIView

        func makeUIView(context: Context) -> UIView { view }

        func updateUIView(_ uiView: UIView, context: Context) { }
    }

    func toPreview() -> some View {
        Preview(view: self)
    }
}

@available(iOS 13.0, *)
extension UIViewController {
    private struct Preview: UIViewControllerRepresentable {
        let viewController: UIViewController

        func makeUIViewController(context: Context) -> UIViewController { viewController }

        func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
    }

    func toPreview() -> some View {
        Preview(viewController: self)
    }
}
#endif


使う側

モデル

PreviewModel.swift
struct PreviewModel {
  var texts: [String] = []
}

Viewで使う

TestPreviewView.swift

import SwiftUI
import UIKit
import SnapKit

final class TestPreviewView: UIView {

  private let labels: [UILabel] = [.init(), .init(), .init(), .init()]

  override init(frame: CGRect) {
    super.init(frame: frame)
    self.setupLayout()
  }

  func configure(model: PreviewModel) {
    model.texts.enumerated().forEach { (index, text) in
      labels[index].text = text
    }
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

private extension TestPreviewView {
  func setupLayout() {

    let stackView: UIStackView = .init()
    self.addSubview(stackView)
    stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
    stackView.axis = .vertical
    stackView.alignment = .fill
    stackView.distribution = .fill

    let customSpaces: [CGFloat] = [30, 10, 40, 0]

    for (idx, label) in labels.enumerated() {
      label.setHuggingCompressionResitanceAll(priority: .required)
      label.numberOfLines = 0
      label.textColor = .black
      stackView.addArrangedSubview(label)
      stackView.setCustomSpacing(customSpaces[idx], after: label)
    }

    let space = UIView()
    stackView.addArrangedSubview(space)
  }
}

#if canImport(SwiftUI) && DEBUG
import SwiftUI

@available(iOS 13.0, *)
struct TestPreviewViewRepresentable_Previews: PreviewProvider {
    static var previews: some View {
      let view1 = TestPreviewView()
      view1.configure(model: PreviewModel(texts: ["label1aaaaa", "label2", "label3", "label4"]))

      let view2 = TestPreviewView()
      view2.configure(model: PreviewModel(texts: ["label1だってばよよおおおおお", "吾輩は猫である", "名前は", "まだないかもしれないんだあああああああああ"]))

      return Group {
          view1.toPreview()
            .previewLayout(.fixed(width: 400, height: 200))
            .previewDisplayName("iPhone X")

          view2.toPreview()
            .previewLayout(.fixed(width: 200, height: 200))
            .previewDisplayName("iPhone SE1")
      }
    }
}
#endif

controllerで使う

TestPreviewViewController.swift
import UIKit

final class TestPreviewViewController: UIViewController {

  private let labels: [UILabel] = [.init(), .init(), .init(), .init()]

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  init() {
    super.init(nibName: nil, bundle: nil)
    self.view.backgroundColor = .systemPink
    self.setupLayout()
  }

  func configure(model: PreviewModel) {
    model.texts.enumerated().forEach {[weak self] (index, text) in
      self?.labels[index].text = text
    }
  }

  private func setupLayout() {
    let stackView: UIStackView = .init()
    self.view.addSubview(stackView)
    stackView.snp.makeConstraints {
      $0.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
      $0.left.equalTo(self.view.safeAreaLayoutGuide.snp.left)
      $0.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom)
      $0.right.equalTo(self.view.safeAreaLayoutGuide.snp.right)
    }
    stackView.axis = .vertical
    stackView.alignment = .fill
    stackView.distribution = .fill

    let customSpaces: [CGFloat] = [30, 10, 40, 0]

    for (idx, label) in labels.enumerated() {
      label.setHuggingCompressionResitanceAll(priority: .required)
      label.numberOfLines = 0
      label.textColor = .black
      stackView.addArrangedSubview(label)
      stackView.setCustomSpacing(customSpaces[idx], after: label)
    }

    let space = UIView()
    stackView.addArrangedSubview(space)
  }
}

#if canImport(SwiftUI) && DEBUG
import SwiftUI

@available(iOS 13, *)
struct PreviewTestViewControllerRepresentable_Previews: PreviewProvider {

  static var previews: some View {
    let vc1 = TestPreviewViewController()
    vc1.configure(model: PreviewModel(texts: ["label1aaaaa", "label2", "label3", "label4"]))

    let vc2 = TestPreviewViewController()
    vc2.configure(model: PreviewModel(texts: ["label1だってばよよおおおおお", "吾輩は猫である", "名前は", "まだないかもしれないんだあああああああああ"]))
    return Group {
      vc1.toPreview()
        .previewDevice(PreviewDeiceEnum.se2.device)
        .previewDisplayName(PreviewDeiceEnum.se2.displayName)

      vc2.toPreview()
        .previewDevice(PreviewDeiceEnum.eleven.device)
        .previewDisplayName(PreviewDeiceEnum.eleven.displayName)

    }
  }
}
#endif

シミュレータ

一覧を表示

xcrun simctl list devicetypes

アノテーション

discardableResult

参考
デフォルトで関数の戻り値を使わない時に警告が出ないようにする

test.swift
func test() -> Int { 1 }
test() // Result of call to 'test()' is unused の警告が出る
test.swift
@discardableResult
func test() -> Int { 1 }
test() // 警告が出ない

プリプロセッサマクロ

swiftのバージョンで切り替え

#if swift(>=4.2)
#else
#endif

クラス名を取得

self.classForCoder
Class Test {
  static var className: String {
    String(describing: Test.self)
  }
}

Optionalにextension

String?ならこんな感じ

extension Optional where Wrapped == String {
    var isEmpty: Bool { self == nil || self!.isEmpty }
}

制約

##コードでautolayout
いつもsnapkitを使うから標準のやり方を忘れる


class NormalViewController: UIViewController {

  //xibファイルを読み込む
  private let headerView: HeaderView = UINib.init(nibName: "Header", bundle: nil).instantiate(withOwner: nil, options: nil).first as! HeaderView
  @IBOutlet weak var headerContainerView: UIView! {
    didSet {
      headerContainerView.addSubview(headerView)

      //falseにする
      headerView.translatesAutoresizingMaskIntoConstraints = false

      //制約をつける
      headerView.topAnchor.constraint(equalTo: headerContainerView.topAnchor, constant: 100).isActive = true
      headerView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor).isActive = true
      headerView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor).isActive = true
      headerView.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor).isActive = true

      //こうやって制約をまとめて、最後にactiveにもできる
//      let constraints = [
//          headerView.topAnchor.constraint(equalTo: headerContainerView.topAnchor),
//          headerView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor),
//          headerView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor),
//          headerView.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor)
//      ]
//      NSLayoutConstraint.activate(constraints)
    }
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    headerView.configure()
  }
}

コードで制約貼る時によく使う処理のextension

まあこれをたくさん用意したのがsnapkitなんだが

struct UIEdgePriorities {
  var top: UILayoutPriority = .required
  var leading: UILayoutPriority = .required
  var trailing: UILayoutPriority = .required
  var bottom: UILayoutPriority = .required
}

extension UIView {
  func widthEqualTo(_ width: CGFloat) {
    self.translatesAutoresizingMaskIntoConstraints = false
    self.widthAnchor.constraint(equalToConstant: width).isActive = true
  }

  func heightEqualTo(_ height: CGFloat) {
    self.translatesAutoresizingMaskIntoConstraints = false
    self.heightAnchor.constraint(equalToConstant: height).isActive = true
  }

  func sizeEqualTo(_ size: CGSize) {
    self.translatesAutoresizingMaskIntoConstraints = false
    let constraints = [
      self.widthAnchor.constraint(equalToConstant: size.width),
      self.heightAnchor.constraint(equalToConstant: size.height)
    ]
    NSLayoutConstraint.activate(constraints)
  }

  func edgesEqualToSuperView(margin: UIEdgeInsets = .zero, priorities: UIEdgePriorities = .init()) {
    guard let _superview = superview else { return }
    self.translatesAutoresizingMaskIntoConstraints = false
    let constraints = [
      self.topAnchor.constraint(equalTo: _superview.topAnchor, constant: margin.top),
      self.leadingAnchor.constraint(equalTo: _superview.leadingAnchor, constant: margin.left),
      _superview.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: margin.right),
      _superview.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: margin.bottom)
    ]
    constraints[0].priority = priorities.top
    constraints[1].priority = priorities.leading
    constraints[2].priority = priorities.trailing
    constraints[3].priority = priorities.bottom

    NSLayoutConstraint.activate(constraints)
  }

  func setHuggingCompressionResitanceAll(priority: UILayoutPriority = .defaultHigh) {
    self.setContentHuggingPriority(priority, for: .horizontal)
    self.setContentHuggingPriority(priority, for: .vertical)
    self.setContentCompressionResistancePriority(priority, for: .horizontal)
    self.setContentCompressionResistancePriority(priority, for: .vertical)
  }
}

UICollectionView, UITableView

画面内のセルで一番上のセルのindxPathを取得

extension UICollectionView {
    // cellHeightRatioはセルがどの程度隠れていたら見えていないセルと判断するか決めるパラメータ
    func getTopVisibleIndexPath(cellHeightRatio: CGFloat = 0.0) -> IndexPath? {

        let index = self.visibleCells.enumerated().filter { (args) -> Bool in
            let (_, cell) = args
            return self.contentOffset.y < (cell.frame.minY + cell.frame.size.height * cellHeightRatio)
        }
        .sorted { (lhs, rhs) -> Bool in
            let lCell = lhs.element
            let rCell = rhs.element
            return lCell.frame.minY < rCell.frame.minY
        }.map { $0.offset }.first

        guard let _index = index else { return nil }
        return self.indexPathsForVisibleItems[_index]
    }
}

extension UITableView {
    func getTopVisibleIndexPath(cellHeightRatio: CGFloat = 0.0) -> IndexPath? {

        let index = self.visibleCells.enumerated().filter { (args) -> Bool in
            let (_, cell) = args
            return self.contentOffset.y < (cell.frame.minY + cell.frame.size.height * cellHeightRatio)
        }
        .sorted { (lhs, rhs) -> Bool in
            let lCell = lhs.element
            let rCell = rhs.element
            return lCell.frame.minY < rCell.frame.minY
        }.map { $0.offset }.first

        guard let _index = index else { return nil }
        return self.indexPathsForVisibleRows?[_index]
    }
}

subscript

参考というか全て解説されてる

配列の要素に安全にアクセス


extension Collection {
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
let arr = [1, 2, 3, 4, 5]
arr[safe: 0] // 1
arr[safe: 5] // nil
arr[5] // crash

配列の要素から安全に抜き出す

extension Array {
    subscript(safe range: ClosedRange<Int>) -> Self {
        guard let first = range.first, let last = range.last,
              self.count > first else {
            return self
        }
        if self.count <= first + last {
            let nRange = first...(self.count - 1)
            return self[nRange].map { $0 }
        }
        return self[range].map { $0 }
    }

    subscript(safe range: Range<Int>) -> Self {
        guard let first = range.first, let last = range.last,
              self.count > first else {
            return self
        }
        if self.count <= last {
            let nRange = first...(self.count - 1)
            return self[nRange].map { $0 }
        }
        return self[range].map { $0 }
    }
}

let arr = [1,2,3,4,5]
arr[safe: 0...2] // [1, 2, 3]
arr[safe: 0..<2] // [1, 2]
arr[safe: 3...5] // [4, 5]
arr[safe: 3..<6] // [4, 5]
arr[3...5] // crash
arr[3..<6] // crash

weak

循環参照を避けるために使うと頭で分かっても定型文的にweak書いてなんでだっけと忘れてしまうのでメモ

クロージャ

インスタンスがクロージャを強参照として持ち、クロージャが内部でselfを使うとインスタンスを強参照として持ってしまうので

class A{
    var count: Int = 0

    init() {
        debugPrint("\(String(describing: type(of:self))) \(#function)")
    }
    deinit {
        debugPrint("\(String(describing: type(of:self))) \(#function)")
    }

    lazy var plusOneNoWeak: () -> Void = { self.count += 1 }
    lazy var plusOneWeak: () -> Void = { [weak self] in self?.count += 1 }
}

//スコープ内で実行して開放するか確認
do{
    let a = A()
    a.plusOneWeak()
    //こっち実行したら開放しなくなっちゃうよ
    //    a.plusOneNoWeak()
}

/*出力
 "A init()"
 "A deinit"
*/

mutating

ここに書いてる通りだけど
struct, enum, extensionで自身の値を変更するfuncの場合にmutatingキーワードを書く

unowned

ここに詳しく
nilにならないweak
unownedweakどっち使うか迷うぐらいならweakにしとけ

Enum

Asociated Valueの取り出し方

ここに詳しく

本来はswitchでいいけどifで取り出したいこともある

.swift
import Foundation

enum Test {
    case one(v: Int)
    case two(v: Int)
}

let test = Test.one(v: 20)

if case .one(let v) = test, v == 10{
	print(v)
}

UIImageの操作

矩形で切り抜き(クロッピング)

UIImage+crop.swift
extension UIImage{
  func cropped(to rect: CGRect) -> UIImage? {
    let scaleRect = CGRect(x: rect.minX * self.scale, y: rect.minY * self.scale, width: rect.width * self.scale, height: rect.height * self.scale)
    guard
      let imgRef = self.cgImage?.cropping(to: scaleRect) else {
        return nil
    }
    return UIImage(cgImage: imgRef, scale: scale, orientation: imageOrientation)
  }
}

Orientaionの修正

UIImage+fixOrientation.swift
extension UIImage{
func fixedOrientation() -> UIImage {

    guard
      imageOrientation != UIImage.Orientation.up,
    let cgImage = self.cgImage,
    let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
      return self
    }

    var transform: CGAffineTransform = CGAffineTransform.identity

    switch imageOrientation {
    case .down, .downMirrored:
      transform = transform.translatedBy(x: size.width, y: size.height)
      transform = transform.rotated(by: CGFloat.pi)
      break
    case .left, .leftMirrored:
      transform = transform.translatedBy(x: size.width, y: 0)
      transform = transform.rotated(by: CGFloat.pi / 2.0)
      break
    case .right, .rightMirrored:
      transform = transform.translatedBy(x: 0, y: size.height)
      transform = transform.rotated(by: CGFloat.pi / -2.0)
      break
    default:
      break
    }

    switch imageOrientation {
    case .upMirrored, .downMirrored:
      transform.translatedBy(x: size.width, y: 0)
      transform.scaledBy(x: -1, y: 1)
      break
    case .leftMirrored, .rightMirrored:
      transform.translatedBy(x: size.height, y: 0)
      transform.scaledBy(x: -1, y: 1)
    default:
      break
    }

    ctx.concatenate(transform)

    switch imageOrientation {
    case .left, .leftMirrored, .right, .rightMirrored:
      ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
    default:
      ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
      break
    }

    guard let newCGImage = ctx.makeImage() else { return self }
    return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
  }
}

どこでもタッチが反応するUISlider

参考

TappableSlider.swift
class TappableSlider: UISlider {
  override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    //タッチした位置にvalueを変更
    let tapPoint = touch.location(in: self)
    let tapValue = min(1.0, max(0.0, tapPoint.x / self.frame.size.width))
    self.value = Float(tapValue)
    // どんなtouchでもスライダー調節を行う
    return true
  }
}

PanGestureの方向

pan+extension.swift
enum PanDirection{
  case up
  case down
  case left
  case right
}

extension UIPanGestureRecognizer{
  func getPanDirection(inView:UIView)->PanDirection{
    let velocity = self.velocity(in: inView)
    return fabs(velocity.y) > fabs(velocity.x) ? 
             (velocity.y > 0 ? .up : .down) : 
             (velocity.x > 0 ? .right : .left)
  }
}

自作オペレータ

参考

// これはオペレータですよと定義
infix operator =~

//そのオペレータの実装
func =~(lhs: String, rhs: String) -> Bool {
/*
何かしらの処理
*/
  return true
}

配列

初期化

参考

var intArray = [Int](repeating: 5, count: 100) //[5, 5, 5,...,5]
var strArray = [String](repeating: "hello", count: 50) //["hello", "hello",...,"hello"]
var intArray2 = Array(1...100) //[1, 2, 3, ..., 98, 99, 100]

ランダム

["あいうえお", "かきくけこ"].randomElement() // "あいうえお" or "かきくけこ" がランダムで

配列から毎回違う要素をランダムで選択

let arr = ["あいうえお", "かきくけこ", "さしすせそ"]
var selectedStrInArr:String=""
selectedStrInArr = arr.filter({$0 != selectedStrInArr}).shuffled().first

結合

  var a = [1,2,3,4,5]
  var b = [10,11,12,13,14,15]

  var c = a + b // [1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15] cに代入
  a.append(contentsOf: b)  //[1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15] aが変わる

  var aa = [[1,2,3],[4,5,6]]
  var bb = [[11,12,13],[14,15,16]]

  var cc = aa + bb // [[1, 2, 3], [4, 5, 6], [11, 12, 13], [14, 15, 16]] ccに代入
  aa.append(contentsOf: bb) // [[1, 2, 3], [4, 5, 6], [11, 12, 13], [14, 15, 16]] aaが変わる
  bb.append(b) //[[11, 12, 13], [14, 15, 16], [10, 11, 12, 13, 14, 15]] bbが変わる

ユニークにする

extension Array where Element: Equatable {
  var unique: [Element] {
    return reduce([Element](), { (result, sequence) in
      result.contains(sequence) ? result : result + [sequence]
    })
  }
}

大文字だけか判定

str.components(separatedBy: CharacterSet.uppercaseLetters.inverted).joined() == str
//もしくは
str.allSatisfy(\.isUppercase)

String

String -> Int

let _ = Int("12") // -> 12
let _ = Int("012") // -> 12
let _ = Int("+12") // -> 12
let _ = Int("-12") // -> -12
let _ = Int("12.3") // -> nil

指定の文字列の数を数える

20200105更新
もっとシンプルな書き方があった

//改行コードの数を数えてみる
let a = "あいうえお\nかきく\nけこ"
let c = a..components(separatedBy: "\n").count - 1 // 2

参考
これもう古い

extension String {
  func numberOfOccurrences(of word: String) -> Int {
    var count = 0
    var nextRange = self.startIndex..<self.endIndex
    while let range = self.range(of: word, options: .caseInsensitive, range: nextRange) {
      count += 1
      nextRange = range.upperBound..<self.endIndex
    }
    return count
  }
}

//改行コードの数を数えてみる
let a = "あいうえお\nかきく\nけこ"
let c = a.numberOfOccurrences(of: "\n") // 2

半角数値(またはそれ以外)の数を数える

全角数値があると数が狂う

let a = "あいうえお10かきく225けこ" //全部で15文字
//半角数値の数
a.components(separatedBy: CharacterSet.decimalDigits).count - 1 //5
//半角数値以外の数
a.components(separatedBy: CharacterSet.decimalDigits.inverted).count - 1 //10

指定した回数だけ同じ文字列を繰り返す

let times = 3
let orgStr = "ABCDEFG"
(0...times).reduce("") { (str, _) -> String in  orgStr + "・" + str }
// -> ABCDEFG・ABCDEFG・ABCDEFG・

指定した長さと文字のランダム文字列を生成

//charactersの中の文字からランダムに選ばれる
func randomString(length: Int, characters:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") -> String {
    return Array(1...length).reduce(into: "", { (str, _) in
      return str.append(characters.randomElement()!)
    })
  }

CGRectの操作


// 元のCGRect
let f1 = CGRect(x: 10, y: 20, width: 30, height: 40) //{x 10 y 20 w 30 h 40}

//dx,dy分、f1の中に入るCGRect (originだけがずれるわけじゃないよ)
let f2 = f1.insetBy(dx: 5, dy: 5) //{x 15 y 25 w 20 h 30}

//insetByの細かく指定できる版
let f3 = f1.inset(by: UIEdgeInsets.init(top: 5, left: 5, bottom: 10, right: 10)) //(15.0, 25.0, 15.0, 25.0)

//指定した位置でframeを分割する 
let f4 = f1.divided(atDistance: 5, from: CGRectEdge.minXEdge) //slice: (10.0, 20.0, 5.0, 40.0), remainder: (15.0, 20.0, 25.0, 40.0))

Autolayout

コードで制約を貼る

ビューの真ん中にボタンを置く例

constraint.swift
    let btn = UIButton.init()
    btn.setTitle("ログイン", for: .normal)
    btn.setTitleColor(.black, for: .normal)

    self.view.addSubview(btn)

    //AutoresizingMaskの設定をconstraintに反映させるかどうか (古い仕様との互換性のためで、今時意味はないので常にfalse
    btn.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      btn.widthAnchor.constraint(greaterThanOrEqualToConstant: 100),//横幅100以上
      btn.heightAnchor.constraint(equalToConstant: 100),//縦幅100
      NSLayoutConstraint(item: btn, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0),//centerXをviewのcenterXに合わせる
      NSLayoutConstraint(item: btn, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0),//centerYをviewのcenterYに合わせる
      //なぜかこれは警告が出る
      //      btn.centerXAnchor.constraint(equalToSystemSpacingAfter: self.view.centerXAnchor, multiplier: 1.0),
      //      btn.centerYAnchor.constraint(equalToSystemSpacingBelow: self.view.centerYAnchor, multiplier: 1.0)
    ])

StackView in CollcectionViewCell

CollectionViewCellの中にStackViewを入れるときはひとつViewを挟む
セルの高さの制約はpriorityを750にしとく

Screenshot from Gyazo

Grand Central Dispatch(GCD)

参考
参考

GCDクラス

gcd.swift
class GCD{
  // 時間のかかる処理
  private static func doSomething(number: Int) {
    print("------- Task \(number): Start -------")
    for i in 0 ... 5 {
      usleep(500000)//0.5sec待ち

      print("Task \(number): Running ... \(i * 20)%")
    }
    print("------- Task \(number): Completed -------")
  }

  // すべて直列で処理
  static func inMainQueueOnly() {
    for i in 1 ... 3 {
      doSomething(number: i)
    }
    print("All tasks completed.")
  }

  // キューを並列で処理
  static func inConcurrentQueue() {
    let queue = DispatchQueue.global(qos: .default)
    for i in 1 ... 3 {
      queue.async {
        self.doSomething(number: i)
      }
    }
    print("ここはキューの処理完了を待たずに実行される")
  }

  // 複数のキューを並列処理した後に、さらに処理を行う
  static func doMoreAfterConcurretQueuesUsingDispatchGroup() {
    let group = DispatchGroup()
    let queue = DispatchQueue.global(qos: .default)

    for i in 1 ... 3 {
      group.enter()
      queue.async {
        self.doSomething(number: i)
        group.leave()
      }
    }
    group.notify(queue: DispatchQueue.global(qos: .default)) {
      print("すべてのキューの処理が完了しました")
    }
  }

  // 何か処理した後に値を返す(セマフォを使用)
  static func returnsValueAfterAQueueUsingSemaphore() -> String {
    var string = "初期値"
    let semaphore = DispatchSemaphore(value: 0)
    let queue = DispatchQueue.global(qos: .default)

    for i in 1 ... 3 {
      queue.sync {
        self.doSomething(number: i)
        string += " => Task\(i)が終了"
        semaphore.signal()
      }
    }

    for _ in 1 ... 3 {
      semaphore.wait()
    }
    return string
  }
  // キューを並列処理した後に値を返す(セマフォを使用)
  static func returnsValueAfterConcurrentQueuesUsingSemaphore() -> String {
    var string = "初期値"
    let semaphore = DispatchSemaphore(value: 0)
    let queue = DispatchQueue.global(qos: .default)
    for i in 1 ... 3 {
      queue.async {
        self.doSomething(number: i)
        string += " => Task\(i)が終了"
        semaphore.signal()
      }
    }
    for _ in 1 ... 3 {
      semaphore.wait()
    }
    return string
  }
}

直列で処理

GCD.inMainQueueOnly()

並列で処理

GCD.inConcurrentQueue()

並列処理した後に処理を実行する

GCD.doMoreAfterConcurretQueuesUsingDispatchGroup()

直列処理した後に値を返す

  let result = GCD.returnsValueAfterAQueueUsingSemaphore()
  print(result)

並列処理した後に値を返す

  let result = GCD.returnsValueAfterConcurrentQueuesUsingSemaphore()
  print(result)

ふたつのTableViewのスクロールを同期させる

RxSwiftありきの実装

    tableView1.rx.didScroll.asObservable()
      .filter({(_) -> Bool in
        self.tableView2.contentOffset.y != self.tableView1.contentOffset.y
      })
      .subscribe(onNext: {[weak self] (_) in
        guard let wSelf = self else{return}
        let x = wSelf.tableView2.contentOffset.x
        let y = wSelf.tableView1.contentOffset.y
        wSelf.tableView2.setContentOffset(CGPoint(x:x, y:y), animated: false)
    }).disposed(by: disposeBag)

    tableView2.rx.didScroll.asObservable()
      .filter({ (_) -> Bool in
        self.tableView2.contentOffset.y != self.tableView1.contentOffset.y
      })
      .subscribe(onNext: {[weak self] (_) in
        guard let wSelf = self else{return}
        let x = wSelf.tableView1.contentOffset.x
        let y = wSelf.tableView2.contentOffset.y
        wSelf.tableView1.setContentOffset(CGPoint(x:x, y:y), animated: false)
    }).disposed(by: disposeBag)

Xcode

Storybaord内の文字列検索

Xcodeではヒットしないのでgrepでやる

grep -i -r --include=*.storyboard <検索文字列> <検索パス>

複数のXcodeがあるときのCLI対応

現在のXcodeのパスを確認

.sh
xcode-select -print-path

Xcodeの切り替え

.sh
sudo xcode-select -switch /Applications/<指定のXcode>/Contents/Developer
30
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?