20201125のSwiftに関する記事は17件です。

【macOS】スクロールできるNSTextViewを生成する

問題

コードでNSTextViewを生成&追加するとき、以下のようにするとテキストが下端まで行ってもスクロールすることができません。

// スクロールできないTextViewの例

class ViewController: NSViewController {
    private let textView = NSTextView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(textView)

        // ウィンドウいっぱいにTextViewを設置
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        textView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        textView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        textView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }
}

解法

スクロールできるNSTextViewを生成するためには、NSTextView.scrollableTextView()メソッドを使い、以下のように実装するとうまくいきます。

// スクロールできるTextViewの例

class ViewController: NSViewController {
    private let scrollView = NSTextView.scrollableTextView()

    private lazy var textView: NSTextView = {
        let view = scrollView.documentView as! NSTextView
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(scrollView)

        // ウィンドウいっぱいにTextViewを設置
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }
}

NSTextViewを継承したクラスの場合

NSTextViewを継承したカスタムTextViewの場合でも、同様にscrollableTextView()メソッドを使用することでtextViewオブジェクトを取り出して扱えます。

// カスタムTextView
class MyTextView: NSTextView {
    ...
}

class ViewController: NSViewController {
    private let scrollView = MyTextView.scrollableTextView()

    private lazy var textView: MyTextView = {
        let view = scrollView.documentView as! MyTextView
        return view
    }()

    ...
}

参考

https://stackoverflow.com/questions/58426739/nstextview-scrolling

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2020-11-25時点でMongoDB RealmをiOSで使う場合の注意点

経緯

いま、iOS/Android(おそらくWebも)対応予定のアプリをprivateで書いています。
クロスプラットフォームでデータを同期するようなアプリとなる予定なので、記事タイトルにあるMongoDB Realmを利用することにしました。

というような経緯で、MongoDB RealmをiOSアプリで導入しようとしたら、2020-11-25時点でいくつか注意する点があったので、メモがてら残しておきます。

Be sure to select MongoDB version 4.4

見出しの通りなのですが、MongoDB Realmでは、MongoDB version 4.4(2020-11-25時点)である必要があります。

MongoDBが2019年にRealmを買収したため、2020年頃にRealmとMongoDBを統合したサービスに刷新され、以下の構成でworkするようになっています。
スクリーンショット 2020-11-25 20.39.07.png
https://www.mongodb.com/realm より

稲妻アイコンがRealmで葉っぱアイコンがMongoDBです。

MongoDB Realm利用者は、AWS / Google Cloud / Azure上にMongoDB用のクラスタを構築する必要があります。
このあたりは、MongoDB RealmがWeb UI(Realm UIと呼ばれています)を提供してくれているので、それ経由で簡単に構築できます。なのでRealm UIに従いサクサク進めていくと、見落としがちなのですが(私がそうでした :cry:)、MongoDBのバージョンは4.4である必要があります。
default versionが4.2になっていたり、いくつかのregionは4.2までしか利用できない、という状況なので、利用する際には気をつけてください :pray:

Choose your preferred provider and region, tier, and additional settings. As you build your cluster, Atlas displays the associated costs at the bottom of the page.

Be sure to select MongoDB version 4.4 for your cluster if you wish to use sync!

https://docs.mongodb.com/realm/get-started/create-realm-app/#b-create-an-atlas-cluster

(ドキュメントにはちゃんと書いてあります。sync機能を使う場合はと記載されているのですが、MongoDB Realmを採用するにも関わらず、sync機能を使わないケースはあまりない気がするので、実質必須と言ってしまって良い気がします。)

SwiftPM経由でRealmを導入できない?

こちらのドキュメントを読みつつiOSアプリにRealmの導入を進めていると、
こちらに記載されている通り、RealmはSwiftPM対応されているとのことなので、SwiftPM経由でRealmを依存に追加しました。

追加後もドキュメントに従い、import文を追加、

import RealmSwift

さらに下記を追加したのですが、Appは見つからない、というようなエラーが出てbuildできませんでした :cry:

let app = App(id: YOUR_REALM_APP_ID) // Replace YOUR_REALM_APP_ID with your Realm app ID

「import文が足りない?SwiftPMにおける依存の追加に誤りがある?」:thinking: などと自身の見落としを疑いドキュメントを改めて読んでみたのですが、特に問題はなさそうでした。
そもそもApp.swiftが存在していないのでは、ということで、realm-cocoaのPackage.swiftを見てみると、App.swiftexcludeされており、こちらが原因のように見えました :cry:
試しにCocoaPods経由で追加すると無事buildが通るようになりました。

ドキュメント上では、SwiftPM経由で追加した場合にはApp.swiftは無い(利用できない)、というようなことは何も記載されていないにも関わらずexcludeされている原因が気になったのでPRを追ってみました。

Package.swiftのexclude対象としてApp.swiftが追加されたのは、こちらのPRですが、このPRではRealmApp -> Appというrenameをしただけのようで、rename前のRealmApp.swiftがexclude対象になったのは、こちらのPRのようでした。
PRコメントとしてはDescription TK.と書かれているのみだったので、
CHANGELOG.mdを読んでみると、このPRでMongoDB統合をサポート開始したようでした。

Add support for next generation sync. Support for syncing to MongoDB instead of Realm Object Server. Applications must be created at realm.mongodb.com

ただ、知りたかったRealmApp.swiftPackage.swiftにてexcludeされるようになった理由については特に記載はなさそうでした。commit logもこれだけで、理由はクリアにならなかったので、issueで聞いてみるとことにしました。

issueを立てようとすると、下記のISSUE_TEMPLATEが存在し、

!!! MANDATORY TO FILL OUT !!!
<!---

**Questions**: If you have questions about HOW TO USE Realm, ask on
[StackOverflow](http://stackoverflow.com/questions/ask?tags=realm),
or in our [Swift Forum](https://forums.realm.io/c/swift) or [Obj-C Forum](https://forums.realm.io/c/objc).

**Feature Request**: Just fill in the first two sections below.

**Bugs**: To help you as fast as possible with an issue please describe your issue
and the steps you have taken to reproduce it in as many details as possible.

-->

## Goals
<!--- What do you want to achieve? -->

## Expected Results
<!--- What did you expect to happen? -->

## Actual Results
<!--- What happened instead?
e.g. the stack trace of a crash
-->

## Steps for others to Reproduce
<!--- What are steps OTHERS can follow to reproduce this issue? -->

## Code Sample
<!---
Provide a code sample or test case that highlights the issue.
If relevant, include your model definitions.
For larger code samples, links to external gists/repositories are preferred.
Alternatively share confidentially via mail to help@realm.io.
Full Xcode projects that we can compile ourselves are ideal!
-->

## Version of Realm and Tooling
<!---
[In the CONTRIBUTING guidelines](https://git.io/vgxJO), you will find a script,
which will help determining some of these versions.
-->
Realm framework version: ?

Realm Object Server version: ?

Xcode version: ?

iOS/OSX version: ?

Dependency manager + version: ?

今回の件だと、テンプレート中に記載があるSwift Forumのほうが適していそう、かつ、ぱっと見、同じような質問はなさそうだったので、そちらで質問してみることにしました。

で、いまWhy is App.swift exclude from Package.swift?というタイトルの投稿をしてみたのですが、どうやら承認が必要なようで、まだ表示されていません。

We've received your new post but it needs to be approved by a moderator before it will appear. Please be patient.

You have 1 post pending.

なにか返信など来たら追記したいと思います。

2020-11-25 22:43追記

当記事を公開していたら、早速返信してもらうことができました。(めちゃくちゃ早い)
https://developer.mongodb.com/community/forums/t/why-is-app-swift-exclude-from-package-swift/12160

スクリーンショット 2020-11-25 22.44.26.png
スクリーンショット 2020-11-25 22.45.30.png

ということで、Realmのsync機能はSPMをサポートしていないようです :pray:
素直にCocoapods or Carthage経由で利用しましょう。
(ドキュメントに書いておいてくれても良さそうな気もしますが、sync機能はβだったりするので、整備中なのかもしれません)

さいごに

なんというか、まとまりのない文章になってしまいましたが、言いたかったのは下記2点です。

  • Be sure to select MongoDB version 4.4
  • SwiftPM経由でRealmを導入できない?

以上です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Swift 実践入門について 第1章

Swift 実践入門のまとめ。
分からない部分の抜粋も記載し、解決できたら随時更新していきます。
なお、ここに記載している以外でも「わけわからん…」となっている部分も多々ありますが、今は必要ない、と言い聞かせて飛ばしています。

第1章
理解度:50%切るかも…
言語の特徴に関して、聞いたこともない単語が多い。
ジェネリクスから始まり、
Objective-Cと連携可能、ツールチェイン、コマンドライン、REPLはサッパリワカラン。

上記は飛ばすとして、以下の内容はいずれ理解する必要があるのかも。

5ページ

max(::)関数は、比較可能な2つの引数を受け取り、大きいほうを戻り値として返します。max(::)関数の宣言は次のようになっています。

func max(_ x: T, _ y: T) -> T where T : Comparable

宣言にあるTは型引数と言い、引数のxとyに応じて具体的な型に置き換えられます。T : ComparableはTがComparableプロトコルに準拠している必要があることを意味します。プロトコルとは型のインタフェースを定義するものであり、Comparableプロトコルに準拠するということは、価どうしの比較のためのインタフェースが用意されていることを意味します。

Swift実践入門は10日で終わらせる予定。ビジネスロジック、Github入門と続く…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SafeAreaのTopまでViewをアニメーションする方法

最近SafeAreaというのを知りました!
iPhoneXが出たことにより、SafeAreaが誕生したみたいです。
こんな問題がありました。
アニメーションさせたときにViewがSafeArea外まで表示されてしまって困っている。
解決したのでここにメモをしていきますね :relaxed:

SafeAreaのサイズ

このオレンジの部分がSafeAreaです。
スクリーンショット 2020-11-25 20.29.50.png

SafeArea取得

この取得で必要になるのがsafeAreaInsetsです!
safeAreaInsetsにTopまたはBottomでSafeAreaの上と下を取得することができます。
こちらを上手く使うと、SafeArea外に入ることなくアニメーションが可能です

let safeAreaTop = self.view.safeAreaInsets.top
let safeAreaBottom = self.view.safeAreaInsets.bottom

SafeAreaのTopまでAnimationさせてみる

ファイル構成はsampleViewController.swiftsampleViewController.xib
のみ使っていきます!⚠️あとのファイルは気にしないでください。

スクリーンショット 2020-11-25 21.05.46.png

まずはViewを置いて、紐付けしていきます。

コードはこんな感じです。

sampleViewController.swift
import UIKit

class sampleViewController: UIViewController {

    @IBOutlet weak var sampleView: sampleHomeView!

    override func viewDidLoad() {
        super.viewDidLoad()

        Animation()
    }



    func Animation() {
       UIView.animate(withDuration: 0.3,
                      delay: 0.0,
                      options: .curveEaseInOut,
                      animations: {
             self.sampleView.frame = CGRect(x: self.view.frame.minX,
                                            y: self.view.safeAreaInsets.top,
                                            width: self.sampleView.frame.width,
                                            height: self.sampleView.frame.height)
                       }
        )
    }
}

Animationの部分でself.View.safeAreaInset.topというコードを書くだけです。
safeAreaの上の部分を取得してViewを動かすことができます!

実際に動いたのがこちらです。
スクリーンショット 2020-11-25 20.16.56.png

このようにSafeArea内にViewが動いてますね(^ ^)
以上SafeAreaのTopまでViewをアニメーションする方法でした!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift5]containsを使って特定の文字列を検索する方法

containsメソッドとは

文字列の中にある特定の文字列を検索するメソッドです。

contains("検索したい文字列")

実用例

var firstText  = "2020年日本一はソフトバンク"
var secondText = "2021年は阪神タイガースが優勝"
var thirdText  = "baseball"

if firstText.contains("ソフトバンク") { //true
  print("含まれているので呼ばれる")
}

if secondText.contains("巨人") { //false
  print("含まれていないので呼ばれない")
}

//以下、lowercased()で大文字小文字を無視して検索

if thirdText.contains("BaseBall".lowercased()) { //true
  print("含まれているので呼ばれる")
}

if thirdText.contains("Soccer".lowercased()) { //false
  print("含まれていないので呼ばれない")
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Leafのテンプレートファイルが読み込まれない

VaporのLeafを使っている時に起きるエラーです。
ファイル構成は正しいはずなのに .leaf が読み込まれず LeafKit.LeafError.Reason.noTemplateExists と表示される場合は

Edit Scheme -> Run -> Options タブの中の Check the "Use custom working directory にチェックを入れ、プロジェクトのルートディレクトリを選択すると .leaf ファイルが読み込まれるようになりました。

checkmark.png

参考: Ask questionsRunning Feather from Xcode causes error

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftによるPOSIX Socketラッパーの設計と汎用化

この記事は、iOSアプリ開発から公開までの流れ の第11章です。

本稿では、Swift 向けの POSIX Socket のラッパーを設計します。
また、今後のアプリ開発で使用可能とするために汎用化も検討したいと思います。

1. POSIX Socket の呼び出し方について

前回、POSIX Socket を使用して簡単な Ping を実装してみました。
そのソースコードにおける以下の 2 つの呼び出し方に着目してみます。

Step 1. ICMP ソケットを作成

POSIXSocket_OnePing.playground - socket(2)
let sockfd = Darwin.socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)
guard sockfd != -1 else {
    print("socket: " + String(cString: strerror(errno)))
    return
}

Step 2. 宛先アドレスを作成
Step 4. Echo request を送信

POSIXSocket_OnePing.playground - sendto(2)
var to = sockaddr_in(sin_len: UInt8(MemoryLayout<sockaddr_in>.size),
                     sin_family: UInt8(AF_INET),
                     sin_port: in_port_t(0).bigEndian,
                     sin_addr: in_addr(s_addr: inet_addr(IP_ADDRESS)),
                     sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
   /*(中略)*/

let sendData = Data(bytes: &echo, count: MemoryLayout<echoRequest>.size)
let tolen = socklen_t(to.sin_len)
let sent = withUnsafePointer(to: &to) { sockaddr_in in
    sockaddr_in.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddr in
        sendData.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> size_t in
            let unsafeBufferPointer = pointer.bindMemory(to: UInt8.self)
            return Darwin.sendto(sockfd, unsafeBufferPointer.baseAddress, sendData.count, 0, sockaddr, tolen)
        }
    }
}
guard sent != -1 else {
    print("sendto: " + String(cString: strerror(errno)))
    return
}

2. 単純型の扱い

socket(2) の呼び出し方を確認してみます。

このシステムコールの引数 domain, type, protocol および復帰値は int 型です。

% man -s 2 socket
SOCKET(2)                   BSD System Calls Manual                  SOCKET(2)
  :
SYNOPSIS
     int
     socket(int domain, int type, int protocol);
  :

一方、Swift のプロトタイプは以下のとおり Int32 型です。
つまり、int が Int32 に対応しているため、特に何も考えずに呼び出すことができたのです。
1.jpeg
その他の代表的な単純型も以下のように対応しています。
なお、C 言語では真偽値を int で表現するのが一般的なため、Swift の真偽値 Bool に対応する C の型はありません(C++ だとあるみたい)。

C の型 Swift の型
char (signed char) Int8 または CChar (CSingedChar)
unsigned char UInt8 または CUnsingedChar
short Int16 または CShort
unsigned short UInt16 または CUnsignedShort
int Int32 または CInt
unsigned int UInt32 または CUnsignedInt
long Int または CLong
unsigned long UInt または CUnsignedLong
long long Int64 または CLongLong
unsigned long long UInt64 または CUnsignedLongLong
float Float または CFloat
double Double または CDouble

3. ポインタ型の扱い

sendto(2) の呼び出し方を確認してみます。

このシステムコールの引数 size_t および復帰値 ssize_t は、Swift のプロトタイプ(以下の画像)ではそれぞれ Int に対応しています。つまり、上述の対応表に従えば、それぞれ long に typedef されているはずです。
ただし、socklen_t については、IntXX や UIntXX に対応している訳ではないため、Swift ではそのまま socklen_t として扱う必要があるようです (なんでだろう???)。

% man -s 2 sendto
SEND(2)                     BSD System Calls Manual                    SEND(2)
  :
SYNOPSIS
     ssize_t
     sendto(int socket, const void *buffer, size_t length, int flags, 
         const struct sockaddr *dest_addr, socklen_t dest_len);
  :

(まぁそれはよいとして、)
重要なのは、UnsafeRawPointer と UnsafePointer の方です。
2.jpeg
Swift には、C 言語のポインタに相当する基本中の基本の機能がありません。
その代わりに、メモリの位置を指すポインタをデータ型(ポインタ型)として扱い、そのポインタ型が指すメモリ位置のデータにアクセスします。

C と Swift の対応関係は以下のとおりです。(T: 構造体を含む任意の型)

C の型 Swift の型
const T * UnsafePointer<T>
T * UnsafeMutablePointer<T>
const void * UnsafeRawPointer
void * UnsafeMutableRawPointer
NULL オプショナル型の nil

さて、ポインタと聞くと避けては通れない malloc と free ですが、上述の Ping での呼び出し方のようにポインタ型を用いてデータを C に受け渡す方法では、メモリの解放の必要はありません。Swift のコンパイラがうまくやってくれるそうです。
ただし、呼び出し先で malloc によりメモリ領域を確保するような場合は、解放しないとメモリリークしてしまいます。つまり、今回の Ping の場合、「Echo の送信関数」「Echo reply の受信関数」を C で作成して Swift から呼び出す実装方式はリスクが高いと言えそうです。(Swift 初心者な私は安全にコトを進めていきます)

なお、この記事では、ポインタ型への変換方法 (withUnsafePointer, withUnsafeBytes など) については説明しません(というか、できません)。
なので、素晴らしいSwiftのポインタ型の解説SwiftでUnsafePointer<T>などのポインタを扱う の記事(Qiita)を読んでお勉強しましょう。

4. ラッパー設計図

ここまで説明してきてラッパー関数の必要性を強く感じますね。
むしろ、ラッパー関数なしでは POSIX Socket を扱うのは無理だと思われます。

例えば、Data 型のデータを sendtoラッパー に渡せば、中でポインタ型に変換してくれて Darwin.sendto を呼び出すようにすると、ポインタ(メモリ)を意識せずにソケット操作を手軽に実行できそうです。
また、sockaddr_in や errno などもラッピングし、Swift に取り込まれていない不足定義 (IP, ICMP) も内包し、さらには Linux の strace 相当のシステムコールトレース機能を装備することで使い勝手を高めることができそうです。
3.jpeg
ん?わざわざラッパーを作るくらいなら CFSocket でいいのでは?
いやいや、前回も考察したように制御メッセージでカーネルタイムスタンプを取得したいので、やはり POSIX Socket を使いますよ!

5. ラッパーの汎用化に向けて

今回の Ping では、アドレス情報は IPv4 のみ、ソケットラッパー関数やソケットオプションも Ping で使用するものに限定して実装します。
しかし、最終的には UNIX ドメインと IPv6 にも対応し、ソケットタイプは TCP,UDP,ICMP にフル対応し、iOS で使用可能なすべてのソケット関数・すべてのソケットオプション・すべての制御メッセージを扱えるようにしたいと思っています。

このソケット基盤の仕様は、別途 Swifty POSIX Socket Foundation で取りまとめ、 GitHub でのプログラム公開を目指します。(予定は未定)

終わり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift製Webフレームワーク Vaporの紹介2020

Vaporとは

Vaporは、Swift製のWebフレームワークです。
公式URLは https://vapor.codes/ で、Vapor 本体のリポジトリ(https://github.com/vapor/vapor) には 

Vapor is a web framework for Swift.
It provides a beautifully expressive and easy to use foundation for your next website, API, or cloud project. 

と説明が書かれています。
Vapor はスター数も多く(2020年12月現在1万9千程度)、gitignore.io などVaporで開発されたアプリケーションも少しずつ増えてきました。
今回はVaporアドベントカレンダー2020の初日として、インストールとHelloWorldをしてみようと思います。

環境構築

for MacOS

公式(https://docs.vapor.codes/4.0/install/macos/)

Xcode(11.4 以上)が必要なのでApp Storeからダウンロードします。(https://apps.apple.com/us/app/xcode/id497799835?mt=12)
また Vapor4 には Swift 5.2 以上が必要なので、バージョンを確認します。

$ swift --version
Apple Swift version 5.3.1 (swiftlang-1200.0.41 clang-1200.0.32.8)
Target: x86_64-apple-darwin20.1.0

次に Vapor のプロジェクト作成が簡単にできる VaporToolBoxを インストールします。

$ brew install vapor
$ brew upgrade vapor  # すでにインストールされている場合

Vapor ToolBoxがインストールできたことを確認します。

$ vapor --help

for Linux

公式(https://docs.vapor.codes/4.0/install/linux/)
Vapor4は以下のLinuxディストリビューション & バージョンでサポートされています。

Version Version Swift Version
Ubuntu 16.04, 18.04 >= 5.2
Ubuntu 20.04 >= 5.2.4
Fedora >= 30 >= 5.2
CentOS 8 >= 5.2.4
Amazon Linux 2 >= 5.2.4

Swift がインストールされた状態でVaporToolBoxを インストールします。

$ git clone https://github.com/vapor/toolbox.git
$ cd toolbox
$ git checkout <desired version>
$ make install

Vapor ToolBoxがインストールできたことを確認します。

$ vapor --help

HelloWorld

環境構築が完了したので早速プロジェクトを作成します。
プロジェクトの作成には new コマンドを使用します。

$ vapor new プロジェクト名

vapor newコマンドを実行すると対話式でプロジェクトを作成することができます。(必要なパッケージが組み込まれた状態でプロジェクトが作成されます。)
-n フラグをつけてプロジェクトを作成するとパッケージが組み込まれない素の状態のプロジェクトを作成することができます。
今回は-n フラグをつけてプロジェクトを作ります。

$ vapor new hello -n

プロジェクト名のディレクトリが作成されれます。
ディレクトリの中に入り、 Package.swift ファイルをXcodeで開きます。

$ cd hello
$ open Package.swift

パッケージのインストールが終わったらビルドボタンを押すことでアプリを実行します。
ブラウザで http://localhost:8080/ にアクセスすると It works! と表示され、アプリケーションが起動していることが確認できます。

Xcodeを使わない場合は以下のコマンドでビルドとアプリケーションの実行をすることができます。

$ cd hello
$ vapor build
$ vapor run
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CocoaPods Version Update時に発生したライトプロテクション関連エラーの回避方法!

事象

CocoaPodsのバージョンが古くインストールできないPodがあったため、CocoaPodsをアップデートしようとしたら、エラー発生!!以下のエラーの解決法法を紹介します。

※今回のリサーチでわかった解決方法は以下の通りですが、なにか他に方法がありましたら教えてください。

You don't have write permissions for the /usr/bin directory

事象発生までの流れ

Cocoapodsのバージョンをアップデートしよう以下のコマンドを実行

$ sudo gem uninstall cocoapods

エラー発生

/usr/bin directory.に書き込む権限がないらしい。

ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /usr/bin directory.

理由

Catalinaでは、Systemファイルにはライトプロテクトがかかっており、書き込みや削除ができない。

引用:
"Catalina has a new file system arrangement where most of the system files are write-protected. Apple uses two partitions, a read-only one for the main system files and a writeable one for other files, and melds them together and presents them as one to the user.
[For details see the WWDC video What's New in Apple Filesystems]"(https://developer.apple.com/videos/play/wwdc2019/710/#,"")

解決方法

$ sudo gem install cocoapods -n /usr/local/bin

"-n"は、既存のファイルを上書きしないという意味。これでライトプロテクトを回避できるのか!

Screen Shot 2020-11-25 at 4.56.24 PM.png
出典:【 mv 】コマンド――ファイルやディレクトリを移動する/名前を変更する

pod setupはしなくてみいいみたいです。

$ pod setup

引用:
"Yes that is intended. The current 1.8.0 pod setup does not do anything and we have kept it if we need it in the future and to avoid breaking systems that depend on it.

I can keep this open with a tiny enhancement that prints out a message the "Setup completed successfully." or something."
Cocoapods GitHub: No output from pod setup #9184

参考文献

1.CocoaPodsのバージョンアップの方法
2.You don't have write permissions for the /usr/bin directory. while installing Cocoapods
3.SIP is disabled, but /usr/bin is write protected anyway (macOS Catalina beta 10.5)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実装する時に気をつけておく基礎的なこと

はじめに

自分が実装したコードを見てよく言われてた基礎的なこと。
コードレビューをもらう前などに、チェックしているリストです。
気づいたことがあれば更新する予定です。

超基礎編

・ selfを付ける

・空行、空白が統一されているか

・メソッド名や変数名がキャメルケースになっているか

// NG例
func appendname() {
    let Name = "なまえ"
}

// OK例
func appendName() {
    let name = "なまえ"
}

・不要な変数、処理、コメントがないか

  • 定義したけど使わなかった変数
  • 実は処理が通っていないメソッド
  • メモ程度で書いていたコメントなど...

ちょっと基礎編

・マークコメントを付ける

例)
  //MARK: - Action       //ここからはActionの処理を実装しますという意味

    @IBAction func numButton(_ sender: UIButton) {
        guard let num = numLabel.text else {
            return
        }
        guard let senderdNum = sender.titleLabel?.text else {
            return
        }
        numLabel.text = num + senderdNum
    }
マークコメントを付けると、選択したマークコメントに飛べます
MARC.gif

・ドックコメントを付ける

    /// ボタンを押下した時の処理       //メソッドなどの処理説明を書く
    /// - Parameter sender: UIButton     
    @IBAction func numButton(_ sender: UIButton) {

        guard let num = numLabel.text else {
            return
        }
        guard let senderdNum = sender.titleLabel?.text else {
            return
        }
        numLabel.text = num + senderdNum
    }

ショートカットキー

ドッグコメント
command⌘ + option + /

・アクセス修飾子を付ける

知っているようで知らないSwift5のアクセス修飾子

・メソッドの命名をわかりやすくする(どんな処理をしているのか)

うまくメソッド名を付けるための参考情報

参考

知っているようで知らないSwift5のアクセス修飾子
うまくメソッド名を付けるための参考情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】超簡単リファクタリングのチェック項目

コードレビューをもらう前などに、
超簡単にリファクタリングをする時のチェック項目をリストアップしていきます。

超簡単編

  • selfを付ける
  • 空行、空白が統一されているか
  • メソッド名や変数名がキャメルケースになっているか
  • マークコメント/ドックコメントを付ける

ちょっと簡単編



メソッドの切り分けなどを行うとより読みやすくなりますが、
まずはここをチェックしてみてください!

参考

知っているようで知らないSwift5のアクセス修飾子
うまくメソッド名を付けるための参考情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

公式ドキュメントのAuto Layout記事にあるStackViewの例を作成してみた!

はじめに

https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH11-SW12
この公式ドキュメントの例を元に、画像のようなレイアウト作成しました。
Stack ViewContent Hugging PriorityContent Compression Resistance Priorityの使い方や機能についてまとめてみようと思います。
スクリーンショット 2020-11-24 20.47.28.png

Stack Viewの使い方

いくつかやり方があるが、公式ドキュメントに載っていたやり方が分かりやすかったのでそのやり方を説明します。
LabeltextFieldStack Viewでまとめるには、
スクリーンショット 2020-11-24 20.57.39.png
二つを選択して
スクリーンショット 2020-11-24 20.58.53.png
[Editor]>[Embed in]>[Stack View]を選択!
スクリーンショット 2020-11-24 21.01.02.png
水平方向のStack Viewができました!
垂直に並べて同じ作業すれば垂直方向のStack Viewができます。
この作業を繰り返してレイアウトを作成していきます。

完成したもの

最初にも載せたこれです。
スクリーンショット 2020-11-24 20.47.28.png

Content Hugging PriorityとContent Compression Resistance Priority

この完成したレイアウトのContent Hugging PriorityContent Compression Resistance Priorityは以下の通りです。
スクリーンショット 2020-11-24 21.05.02.png

Content Hugging Priority

Stack View内でパーツを配置して、Stack Viewの方が水平または垂直方向に対して大きく、余白が発生する場合の余白を埋めるためのものです。このHuggingの値が小さい方が空白を埋めるために広がるイメージです。
以下の画像はLabelとtextFieldの間が空いていてどちらかに余白を埋めさせるとします。
textFieldのContent Hugging PriorityをLabelより低くして、Stack Viewの左右の幅を決めると、画像のようにtextFieldが余白を埋めてくれます。
スクリーンショット 2020-11-24 21.33.36.png
LabelのHorizontalのContent Hugging Priorityの値は251。(画面一番右の下の方)
スクリーンショット 2020-11-24 21.36.55.png
textFieldのContent Hugging Priorityの値は250
スクリーンショット 2020-11-24 21.39.14.png

Content Compression Resistance Priority

こちらはパーツがStack Viewからはみ出るときに、優先して表示する方の値を大きくします。
labelのContent Compression Resistance Priorityを751、textFieldを750にすると、labelがまず優先的に表示されてからtextFieldが表示されます。
スクリーンショット 2020-11-25 5.44.30.png
逆に、labelを750、textFieldを751にするとtextFieldが優先的に表示され、今回のようにtextFieldの内容が長ければlabelを隠してしまいます。
公式ドキュメントで作成したレイアウトで、labelのResistanceが750でtextFieldが749になっている理由もこれと同じでしょう。
スクリーンショット 2020-11-25 5.41.36.png

最後に

この辺りの知識は使いこなすことができれば、レイアウトの作成がやりやすくなり思うように動かせると思うので、しっかり使いこなしたいですね。

参考サイト

https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH11-SW12
https://blog.officekoma.co.jp/2019/02/stackview-content-hugging.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift Classのまとめ

class

swiftのクラスに関してまとめたのこちらに記事にいたします。

Class 参照型のデータ構造

  • 参照型、継承できる
  • 構造体と列挙体と違うは プロパティやメソッドの文法、初期化のフローなどが違う
  • 上記は継承を考慮するため
  • CocoaフレームワークのほとんどはClassで定義されている
class SomeClass {
    let id: Int
    let name: String

    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }

    func printName() {
        print(name)
    }
}

let instane = SomeClass(id: 1, name: "name")
instane.printName()

継承

  • 型の構成要素の引き継ぎ
  • 継承とは、新たなクラスを定義する時、他のクラスのプロパティ、メソッド、イニシャライザなどの型を再利用する仕組み
  • 継承先のクラスでは、継承元のクラスと共通する動作を改めて定義する必要がなく、継承元のクラスとの差分のみを定義知れば済む
  • 継承元のクラスのサブクラス、継承元のクラスはクラスのスーパークラスといいます
  • Swiftでは複数のクラスから継承する多重継承は禁止されている
class ClassName: 継承 {
    クラスの定義
}

class User {
    let id: Int

    var massage: String {
        return "Hello"
    }

    init(id: Int) {
        self.id = id
    }

    func printProfile() {
        print("id: \(id)")
        print("massage: \(massage)")
    }
}

// Userを継承したクラス
class RegisteredUser: User {

    let name: String

    init(id: Int, name: String) {
        self.name = name
        super.init(id: id)
    }
}

let registeredUser = RegisteredUser(id: 1, name: "tanaka takuya")

let id = registeredUser.id
let massage = registeredUser.massage // Userを継承しているのでmassageが呼べる
registeredUser.printProfile()

オーバーライド

  • 型の構成要素の再定義
  • スーパークラスで定義されているプロパティやメソッドなどの定義は、サブクラスで再定義することもできる
  • オーバーライド可能なプロパティはインスタンスプロパティと後述するクラスプロパティのみで、すタティックプロパティはオーバーライドできない
  • オーバーライドを行うには、overrideキーワードを使用してスーパークラスで定義されている要素を再定義します。
class ClassMame: superClass {
    override func methodName(引数) -> 戻り値 {
    メソッド呼び出し時に実行される文
    }
}

override var プロパティ名型名 {
    get {
        return文によって値を返す処理
        superキーワードでスーパークラスの実装に利用できる
    }

    set {
        値を更新する処理
        superキーワードでスーパークラスの実装を利用できる
    }
}
  • オーバーライドしたプロパティやメソッドの中でsuperキーワードを使用することで、スーパークラスの実装を呼び出すこともできる
  • User1で定義されているmassageとprintProfileをRegisteredUser1でオーバーライドしている
  • printProfileではsuper.printProfileによって、スパークラスの実装を読んでいる
class User1 {
    let id: Int

    var massage: String {
        return "Hello"
    }

    init(id: Int) {
        self.id = id
    }

    func printProfile() {
        print("id: \(id)")
        print("massage: \(massage)")
    }
}

class RegisteredUser1: User1 {
    let name: String

    override var massage: String {
        return "Hello, my name is \(name)"
    }

    init(id: Int, name: String) {
        self.name = name
        super.init(id: id)
    }

    override func printProfile() {
        super.printProfile()
        print("name: \(name)")
    }
}

let user1 = User1(id: 1)
user1.printProfile()

print("--")

let registeredUser1 = RegisteredUser1(id: 2, name: "tanaka takuya")
registeredUser1.printProfile()

  • Userクラスのprofileではidとmessageのみを出力していましたが、ResisteredUser1ではこれに加えてnameの値も出力している

final 継承とオーバーライドの禁止

  • オーバーライド可能な要素の前にfinalをつけることによって、その要素がオーバーライドされることを禁止できる
class SuperClass {
    func overrideMethod() {}

    final func finalMethod() {}
}

class SubClass: SuperClass {
    override func overrideMethod() {}

    // コンパイルerror
    // Instance method overrides a 'final' instance method
    // override func finalMethod() {}
}

// classにfinalをつけることによって、そのクラスを定義することを禁止する
class AAA {}

class BBB: AAA {}

final class CCC {}

// コンパイルerror
// nheritance from a final class 'CCC'
// class DDD: CCC {}

クラスに紐づく要素

  • クラスのインスタンスではなく、クラス自身に紐ずく要素として、クラスプロパティとクラスメソッドがある
  • クラスプロパティとすタティックプロパティと、クラスメソッドはスタティックメソッドと、それぞれ似た性質を持っている
両者の違い
  • スタティックプロパティとスタティックメソッドはオーバーライドできない
  • クラスプロパティとクラスメソッドはオーバーライドできる

クラスプロパティ

  • クラス自身に紐ずくプロパティ
  • クラスプロパティはクラスのインスタンスではなく、クラス自身に紐ずくプロパティで、インスタンスに依存しない値を扱う場合に利用する
  • クラスプロパティを定義するには、プロパティ宣言の先頭にclassをつける
  • アクセス方法は型名に . とクラスプロパティ名をつけて、型名.クラスプロパティのように書く
class A {
    class var className: String {
        return "A"
    }
}

class B: A {
    override class var className: String {
        return "B"
    }
}

A.className // "A"
B.className // "B"

クラスメソッド

  • クラスに紐ずくメソッド
  • クラスメソッドはクラスのインスタンスではなく、クラス自身に紐ずくメソッドで、インスタンスに依存しない処理を実装する際に利用します
  • 使い方は上と同じ
class A1 {
    class func inheritanceHierarchy() -> String {
        return "A"
    }
}

class B1: A1 {
    override class func inheritanceHierarchy() -> String {
        return super.inheritanceHierarchy() + "<-B"
    }
}

class C1: B1 {
    override class func inheritanceHierarchy() -> String {
        return super.inheritanceHierarchy() + "<-C"
    }
}

A1.inheritanceHierarchy() // A
B1.inheritanceHierarchy() // A<-B
C1.inheritanceHierarchy() // A<-B<-C

イニシャライザの種類と初期化のプロセス

  • イニシャライザの役割は型のインスタンス化の完了までに全てのプロパティを初期化し、型の生合成を保つことである
  • クラスには継承関係があるため、様々な階層で定義されたプロパティが初期化されることを保証する必要がある
  • 上記を保証するためにクラスには二段階初期化の仕組みが導入されている
  • 二段階初期化を実現するためにクラスのイニシャライザは指定イニシャライザとコンビニエンスイニシャライザの二種類がある

指定イニシャライザ 主となるイニシャライザ

  • クラスの主となるイニシャライザ
  • このイニシャライザの中で全てのストアドプロパティが初期化される必要がある
class Mail {
    let form: String
    let to: String
    let title: String

    // 指定イニシャライザ
    init(form: String, to: String, title: String) {
        self.form = form
        self.to = to
        self.title = title
    }
}

コンビニエンスイニシャライザ

  • 指定イニシャライザをラップするイニシャライザ
  • 指定イニシャライザを中継するイニシャライザで、内部で引数を組み立てて指定イニシャライザを呼び出す必要がある
class Mail1 {
    let from: String
    let to: String
    let title: String

    init(from: String, to: String, title: String) {
        self.from = from
        self.to = to
        self.title = title
    }

    convenience init(from: String, to: String) {
        self.init(from: from, to: to, title: "Hello, \(from)")
    }
}

let mail = Mail(form: "aaa", to: "bbb", title: "ccc")
let mail1 = Mail1(from: "aaa", to: "bbb")
print(mail.form, mail.to, mail.title)
print(mail1.from, mail1.to, mail1.title)

二段階初期化

  • 型の生合成を保った初期化を実現するため、クラスのイニシャライザには3つのルールがある
  • 指定イニシャライザは、スーパークラスの指定イニシャライザを呼ぶ
  • コンビニエンスイニシャライザは同一クラスのイニシャライザを呼ぶ
  • コンビニエンスイニシャライザは、最終的に指定イニシャライザを呼ぶ
上記のルールを満たしている場合
  • 継承関係にある全てのクラスの指定イニシャライザがかなさず実行され、各クラスで定義されたプロパティが全て初期化されることが保証される
  • 一つでも満たせないルールがある場合、型の生合成を保てない可能性があるのでコンパイルエラーになる
  • またスーパークラスとサブクラスのプロパティの初期化順序を守ため、指定イニシャライザによるクラスの初期化は二段回に分けて行われる
  • クラス内で新たに定義された全てのストアドプロパティを初期化し、スーパークラスの指定イニシャライザを実行する
  • スーパークラスでも同様の初期化を行い、大元のクラスまで遡る
  • ストアドプロパティ以外の初期化を行う
class User2 {
    let id: Int

    init(id: Int) {
        self.id = id
    }

    func printProfile() {
        print("id: \(id)")
    }
}

class RegisteredUser2: User2 {
    let name: String

    init(id: Int, name: String) {
        // 第一段階
        self.name = name
        super.init(id: id)
        // 第二段階
        super.printProfile()
    }
}

デフォルトイニシャライザ

  • プロパティの初期化が不要な場合に定義されるイニシャライザ
  • プロパティが存在しない場合や、全てのプロパティが初期値を持っている場合、指定イニシャライザ内で初期化する必要があるプロパティはない
  • 暗黙的にデフォルトの指定イニシャライザが定義される
class User3 {
    let id = 0
    let name = "kenta"

    // 以下と同等のイニシャライザが自動的に定義されている
    // init() {}
}

let user3 = User3()

// 一つでも指定イニシャライザ内で初期化が必要なプロパティが存在する場合、デフォルトイニシャライザがなくなり、指定イニシャライザを定義する必要があるます!
class User4 {
    let id: Int
    let name: String
    // これがないとコンパイルエラー
    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
}

let user4 = User4(id: 0, name: "Taro")

デイニシャライザ インスタンスの終了処理

  • ARCによってインスタンスが破棄されるタイムングでは、クラスのデイニシャライザが実行されます、デイ二シャライザの逆で、クリーンアップなどの終了処理を行うもの - デイニシャライザは、deinitキーワードを使用して、クラス内に次のように定義する
class ClassName {
//    deinit {
//        クリーンアップなどの処理
//    }
//}
// デイニシャライザは継承関係の下位クラスから自動的に実行されるため、
// スーパークラスのデイ二シャライザを呼び出す必要はありません
// 値の比較と参照の比較
// 参照型の比較は、参照先の値同士の比較と、参照先の比較の2つに分けられる
// 参照先の値同士の比較はこれまでに登場してきたものと同様に==演算子で行い、参照自体の比較は===演算子で行う
class SomeClassA: Equatable {
    static func ==(lhs: SomeClassA, rhs: SomeClassA) -> Bool {
        return true
    }
}

let a = SomeClassA()
let b = SomeClassA()

let c = a

// 同じ
a == b // true
// 参照は異なる
a === b // false
// 参照先は同じ
a === c // true

まとめ

個人的にまとめたので、記事にしてみました。
完全に個人メモをそのまま書いたので、見辛いかも知れませんがご了承ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swift 構造体まとめ

構造体

Swiftの構造体に関してまとめたので、記事として残しておきます。

構造体とは?

  • 値型
  • ストアドプロパティ の組み合わせによって、一つの値を表す
  • 標準ライブラリで提供されている多くは構造体である
    • Bool String Int Array Dictionary
    • オプショナルは列挙型 タプルは型の組み合わせ Anyはプロトコル

定義

struct 構造体名 {
  構造体の定義
}


struct Article {
    let id: Int
    let title: String
    let body: String

    init(id: Int, title: String, body: String) {
        self.id = id
        self.title = title
        self.body = body
    }

    func printBody() {
        print(body)
    }
}

let article = Article(id: 1, title: "title", body: "body")
article.printBody() // body

  • ストアドプロパティ の変更による値の変更
  • 構造体はストアドプロパティの組み合わせで一つの値を表す値型
  • 構造体のストアドプロパティを変更することは、構造体を別の値に変更することであり、構造体が入っている変数や、定数への再代入を必要とします
  • 値型の変更に関する仕様は構造体のストアドプロパティ の変更にも適用される

  • 定数のストアドプロパティは変更できない

  • 構造体のストアドプロパティの変更は再代入を必要とするため、定数に代入された構造体のストアドプロパティは変更できない

struct SomeStruct {
    var id: Int

    init(id: Int) {
        self.id = id
    }
}

var variable = SomeStruct(id: 1)
variable.id = 2
let constant = SomeStruct(id: 1)
constant.id = 2 // error
  • メソッド内のストアドプロパティの変更にはmutatingが必要
  • 構造体のストアドプロパティの変更は再代入が必要なので、ストアドプロパティを含むメソッドにはmutatingが必要
struct SomeStruct2 {
    var id: Int

    init(id: Int) {
        self.id = id
    }
//  mutating をつけないとerror
// Cannot assign to property: 'self' is immutable
// 自動でつけてくれるようになる
    mutating func someMethod() {
        id = 4
    }
}

var some2 = SomeStruct2(id: 1)
some2.someMethod()
some2.id // 4 変更されている
  • メンバーワイズイニシャライザ デフォルトで用意されているイニシャライザ
  • 型のインスタンスは初期化後に全てのプロパティ が初期化される必要がある
  • 独自にイニシャライザを定義して初期化の処理を行うこともできます、
  • 構造体では自動的に定義される メンバーワイズイニシャライザというイニシャライザがある
  • 型が持っている各ストアドプロパティと同名の引数をとるイニシャライザである
struct Article2 {
    var id: Int
    var title: String
    var boby: String

// 以下と同等のイニシャライザが自動的に定義される
//    init(id: Int, title: String, body: String) {
//        self.id = id
//        self.title = title
//        self.boby = body
//    }
}

let article2 = Article2(id: 1, title: "Hello", boby: "this is body")
article2.id      // 1
article2.title   // Hello
article2.boby    // this is body
  • メンバーワイズイニシャライザのデフォルト引数
  • ストアドプロパティ が初期化式とともに定義されている場合
  • そのプロパティに対するメンバーワイズイニシャライザの引数はデフォで引数を持ち、呼び出し時の引数の指定の省略ができる
struct Mail {
    var subject: String = "(No Subject)"
    var body: String

// 以下と同等のイニシャライザが自動的に定義される
//    init(subject: String = "(No Subject)", body: String ) {
//        self.subject = subject
//        self.body = body
//    }
}

let noSubject = Mail(body: "Hello")
noSubject.subject // "(No Subject)"
noSubject.body    // Hello
let greeting = Mail(subject: "Greeting", body: "Hello!")
greeting.subject // Greeting
greeting.body    // Hello!

まとめ

簡単にまとめたのでこちらに記載しました。
何かあればご指摘ください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift 構造体まとめ

構造体

Swiftの構造体に関してまとめたので、記事として残しておきます。

構造体とは?

  • 値型
  • ストアドプロパティ の組み合わせによって、一つの値を表す
  • 標準ライブラリで提供されている多くは構造体である
    • Bool String Int Array Dictionary
    • オプショナルは列挙型 タプルは型の組み合わせ Anyはプロトコル

定義

struct 構造体名 {
  構造体の定義
}


struct Article {
    let id: Int
    let title: String
    let body: String

    init(id: Int, title: String, body: String) {
        self.id = id
        self.title = title
        self.body = body
    }

    func printBody() {
        print(body)
    }
}

let article = Article(id: 1, title: "title", body: "body")
article.printBody() // body

ストアドプロパティ の変更による値の変更

  • 構造体はストアドプロパティの組み合わせで一つの値を表す値型
  • 構造体のストアドプロパティを変更することは、構造体を別の値に変更することであり、構造体が入っている変数や、定数への再代入を必要とします
  • 値型の変更に関する仕様は構造体のストアドプロパティ の変更にも適用される

  • 定数のストアドプロパティは変更できない

  • 構造体のストアドプロパティの変更は再代入を必要とするため、定数に代入された構造体のストアドプロパティは変更できない

struct SomeStruct {
    var id: Int

    init(id: Int) {
        self.id = id
    }
}

var variable = SomeStruct(id: 1)
variable.id = 2
let constant = SomeStruct(id: 1)
constant.id = 2 // error
  • メソッド内のストアドプロパティの変更にはmutatingが必要
  • 構造体のストアドプロパティの変更は再代入が必要なので、ストアドプロパティを含むメソッドにはmutatingが必要
struct SomeStruct2 {
    var id: Int

    init(id: Int) {
        self.id = id
    }
//  mutating をつけないとerror
// Cannot assign to property: 'self' is immutable
// 自動でつけてくれるようになる
    mutating func someMethod() {
        id = 4
    }
}

var some2 = SomeStruct2(id: 1)
some2.someMethod()
some2.id // 4 変更されている

メンバーワイズイニシャライザ

  • デフォルトで用意されているイニシャライザ
  • 型のインスタンスは初期化後に全てのプロパティ が初期化される必要がある
  • 独自にイニシャライザを定義して初期化の処理を行うこともできます、
  • 構造体では自動的に定義される メンバーワイズイニシャライザというイニシャライザがある
  • 型が持っている各ストアドプロパティと同名の引数をとるイニシャライザである
struct Article2 {
    var id: Int
    var title: String
    var boby: String

// 以下と同等のイニシャライザが自動的に定義される
//    init(id: Int, title: String, body: String) {
//        self.id = id
//        self.title = title
//        self.boby = body
//    }
}

let article2 = Article2(id: 1, title: "Hello", boby: "this is body")
article2.id      // 1
article2.title   // Hello
article2.boby    // this is body
  • メンバーワイズイニシャライザのデフォルト引数
  • ストアドプロパティ が初期化式とともに定義されている場合
  • そのプロパティに対するメンバーワイズイニシャライザの引数はデフォで引数を持ち、呼び出し時の引数の指定の省略ができる
struct Mail {
    var subject: String = "(No Subject)"
    var body: String

// 以下と同等のイニシャライザが自動的に定義される
//    init(subject: String = "(No Subject)", body: String ) {
//        self.subject = subject
//        self.body = body
//    }
}

let noSubject = Mail(body: "Hello")
noSubject.subject // "(No Subject)"
noSubject.body    // Hello
let greeting = Mail(subject: "Greeting", body: "Hello!")
greeting.subject // Greeting
greeting.body    // Hello!

まとめ

簡単にまとめたのでこちらに記載しました。
何かあればご指摘ください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS14】CollectionViewのscrollToItemが機能しない場合の対処法

iOS14において、CollectionViewのscrollToItemが機能しないバグが報告されています(執筆時、2020年11月25日現在)。AppleのDeveloper Forumsでも話題になっており、iOS14.2でも修正されていないようです。私もOS依存のバグとは気づかず、仕事で10時間くらい時間を溶かしました(ずっとiOS14のシミュレータでなんでスクロールできないんだろうと四苦八苦してた)

https://developer.apple.com/forums/thread/663156

scrollToItemの説明(読み飛ばし可)

scrollToItemは指定したindexPathまでcellをスクロールさせることができるものです。アプリ初回起動時のウォークスルー、チュートリアル画面などで、スワイプではなくボタンタップでcellをスクロールさせるような場合に使ったりすると思います。

例えば上記のような画面で「次へ」を押した時にページをスクロールするような場合は、以下のように書けます。

collectionView.scrollToItem(at: IndexPath(item: newPage, section: 0), at: .init(), animated: true)

しかしiOS14では、「次へ」を押してもなぜかスクロールされない状態となっています。

解決方法

フォーラムでは下記のようにscrollToItem前後でisPagingEnableのBool値を切り替えるような手法が提案されています。

self.collectionView.isPagingEnabled = false
self.collectionView.scrollToItem(at: indexPath, at: .left, animated: false)
self.collectionView.isPagingEnabled = true

実際試すとこれでもうまくいくのですがこれよりも、冒頭のようなチュートリアル画面を作りたい場合は、同じくcollectionViewのsetContentOffsetで代用する方が良いのではないかなと思いました。

let cellWidth = collectionView.frame.width * CGFloat(newPage)
let cellHeight = CollectionView.frame.minY
collectionView.setContentOffset(CGPoint(x: cellWidth, y: cellHeight), animated: true)

チュートリアル画面ではなくとも今までscrollToItemを使っていた実装は、同じように座標を取得する方法が有効かと思います。

おそらくすぐにAppleが対応してくれる問題だとは思うのですが、もしお困りの方がいらっしゃったら試してみてください〜

参考

UICollectionViewでチュートリアル画面を作る

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XibファイルのButtonを配置してActionさせる方法 addTarget

Xibで書いたらViewControllerに紐付けできない!!どうしようと悩んでいて解決したのでメモります。

ファイル構成

青いViewの読み込みしたいファイルがsampleHomeView.Swift sampleHomeView.xib
ViewControllerのファイルがsampleViewController.swift sampleView.Xibです。
この4つファイルで説明します。
スクリーンショット 2020-11-25 1.19.53.png

解説

こちらを参考にXibをViewに読み込ませてみてください!
       ↓↓↓↓↓↓↓↓↓↓
Xibファイルで作ったUIをViewに読み込む方法

読み込ませたらsampleHomeView.xibにUIButtonを置きましょう。
スクリーンショット 2020-11-25 1.33.59.png

Buttonを置いたら、sampleHomeView.swiftに紐付けしていきます!注意⚠️Outletです。
スクリーンショット 2020-11-25 1.36.21.png
その次にsampleViewController.xibで紐付けをします!
TypeをUIViewではなく、読み込ませているsampleHomeViewに設定します。
スクリーンショット 2020-11-25 0.49.15.png

XibファイルからViewControllerに引っ張って紐付けすることができないので違う方法で紐付けます!

sampleHomeViewと書くとプロパティーで先ほど紐付けしたボタンが
sampleViewControllerの中でsampleButtonが呼べるようになります。

for 今回はタップした時に呼ばれます。

@objc funcの中に処理を書きます。

Xibでやる場合はIBActionではないんですね〜!!
コードはこんな感じです。

sampleViewController.swift
import UIKit

class sampleViewController: UIViewController {

    @IBOutlet weak var sampleHomeView: sampleHomeView!
    @IBOutlet weak var sampleView: sampleHomeView!

    override func viewDidLoad() {
        super.viewDidLoad()

        sampleHomeView.sampleButton.addTarget(self, action: #selector(sampleViewController.tapSampleButton(sender:)), for: .touchUpInside)
    }

    @objc func tapSampleButton(sender:Any) {
        sampleView.backgroundColor = UIColor.red
    }

}


ちなみにsampleHomeView.swiftのコードはこんな感じ

sampleHomeView.swift
import UIKit

class sampleHomeView: UIView {

    @IBOutlet weak var sampleButton: UIButton!

    override init(frame: CGRect) {
        super.init(frame: frame)
        loadView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadView()
    }

    private func loadView() {
        let className = String(describing: type(of: self))
        let view: UIView = Bundle.main.loadNibNamed(className, owner: self, options: nil)?.first as? UIView ?? UIView()
        view.frame = bounds
        addSubview(view)
    }
}


今回ではボタンを押すと背景色が赤になるというようになっています。
スクリーンショット 2020-11-25 1.58.11.png
このようにXibファイルでButtonを使ってみました!!
以上、XibファイルのButtonを配置してActionさせる方法でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む