見出し画像

Swift -アクセス修飾子の@IBOutletから間接的に、ViewController上に参照するには?-

はじめに

SwiftLintを導入した際に、@IBOutletの変数に「アクセス修飾子Privateを追記してください」と警告が発生しました。修正した所、警告は消えたが、エラーが発生してしまった。
エラー原因は、追記したアクセス修飾子Privateでした。
このWarning&Errorの無限ループを抜け出す方法の調査と考察を行ったので、忘れない様にこの記事に纏めて置きます。

使用環境

● OS:macOS Big Sur 11.3.1
● Xcode:12.5
● Swift:5.4
● DB Browser for SQLite:3.12.1

アクセス修飾子について

こちらの記事に纏めてありますので、参考にしてみて下さい。
Swift -アクセス修飾子について-

可読性と安全性

ViewController上でTableViewCellを設定する際に、直接、TableViewCellで宣言した@IBOutlet等に接続すると思いますが、これだと可読性と安全性が悪いと考えられます。
なぜなら、修正しにくいのとファイル間を移動しながら実装する為、可読性と安全性が下がります。
可読性と安全性を保つには、アクセス修飾子を利用して、定数や変数のアクセス制限を設けることで、定数や変数が扱える許容範囲が分かる為、実装しやすくなります。

今回の問題点

こちらのサイトで記載してある内容と同様な問題が発生しました。
Private IBOutlets Swift - Stack Overflow

英語で書かれている為、読むのが大変ですが、一度熟読してみて下さい。
要するに問題点は、下記の3点になります。
①TableViewCell上にあるPrivateを持つ@IBOutletはViewController上で接続ができない
②Privateを持つ@IBOutletをスコープ外で扱うには如何すれば良いのか?
③実際の実装環境にどの様に組み込むか?

①〜③の解決方法を説明して行きます。

解決方法

①TableViewCell上にあるPrivateを持つ@IBOutletはViewController上で接続ができない

Privateを持つ@IBOutletは、宣言したスコープ内のみアクセスが可能である為、スコープ外であるViewController上ではアクセス不可になります。
直接的に接続するのではなく、間接的に接続する方法に持っていく必要があります。

②Privateを持つ@IBOutletをスコープ外で扱うには如何すれば良いのか?

結論から言うと、メソッドを作りましょう。
メソッドとは、インスタンスのプロパティを操作する方法です。

③実際の実装環境にどの様に組み込むか?

人によって実装環境が違うため、適切な説明が出来ませんが、まずはメソッドの概念を知る事と、どこにインスタンスを配置し間接的に接続するかをノートに纏めてから実装してみる。
もし接続できなかったら、インスタンスの配置場所と作成したメソッドが間違っていないか確認する。

●実際に書いたメモ

新規ドキュメント 2021-06-29 00.23.47_新規ドキュメント 2021-06-29 00.23.47

実際にコードを書いてみる

アクセス修飾子なしの実装

// ViewController.swift

import UIKit

class ViewController: UIViewController {
   @IBOutlet weak var friendsView: UITableView!
   
   //配列の作成
   let friends = ["Sasha", "Takeru", "Michael", "Shigeru", "Ghana", "Gaia", "Cecil", "Matumoto"]
   
   override func viewDidLoad() {
       super.viewDidLoad()
       friendsView.delegate = self
       friendsView.dataSource = self
       friendsView.allowsSelection = false
       //friendsView.separatorStyle = .none
   }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
   func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
       return 120
   }
   
   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return friends.count
   }
   
   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       let cell = friendsView.dequeueReusableCell(withIdentifier: "customCell") as! CustomCell
       
       let friend = friends[indexPath.row]
       cell.avatarImg.image = UIImage(named: friend)
       cell.nameLbl.text = friend
       
       return cell
   }
      
}
// CustomCell.swift

import UIKit

class CustomCell: UITableViewCell {
   @IBOutlet weak var friendView: UIView!
   @IBOutlet weak var nameLbl: UILabel!
   @IBOutlet weak var avatarImg: UIImageView!
   
   override func awakeFromNib() {
       super.awakeFromNib()
       // Initialization code
   }

   override func setSelected(_ selected: Bool, animated: Bool) {
       super.setSelected(selected, animated: animated)

       // Configure the view for the selected state
   }

}

●アクセス修飾子付きの実装

// ViewController.swift

import UIKit

class ViewController: UIViewController {
   @IBOutlet weak var friendsView: UITableView!
   
   //配列の作成
   let friends = ["Sasha", "Takeru", "Michael", "Shigeru", "Ghana", "Gaia", "Cecil", "Matumoto"]
   
   override func viewDidLoad() {
       super.viewDidLoad()
       friendsView.delegate = self
       friendsView.dataSource = self
       friendsView.allowsSelection = false
       //friendsView.separatorStyle = .none
   }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
   func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
       return 120
   }
   
   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return friends.count
   }
   
   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       let cell = friendsView.dequeueReusableCell(withIdentifier: "customCell") as! CustomCell
       
       let friend = friends[indexPath.row]
       cell.avatarImg.image = UIImage(named: friend)
       
       // nameLbl.textを持ってくる
       let datasource = CustomCell.SomeVCDataSource(friendName: friend)
       // 間接的にnameLbl.textを繋げる
       cell.nameMethod(with: datasource)
       
       return cell
   }
   
}
// CustomCell.swift

import UIKit

class CustomCell: UITableViewCell {
   @IBOutlet weak var friendView: UIView!
   
   // アクセス修飾子private
   @IBOutlet private weak var nameLbl: UILabel!
   
   @IBOutlet weak var avatarImg: UIImageView!
   
   // methodの作成
   func nameMethod(with dataSource: SomeVCDataSource) {
       guard let nameLbl = nameLbl else { return }
       
       nameLbl.text = dataSource.friendName
       
   }
   
   override func awakeFromNib() {
       super.awakeFromNib()
       // Initialization code
   }

   override func setSelected(_ selected: Bool, animated: Bool) {
       super.setSelected(selected, animated: animated)

       // Configure the view for the selected state
   }
   
   // name.textのデータを保持する構造体
   struct SomeVCDataSource {

       var friendName: String
       
   }
   
}

実装方法は大分違いますが、シュミレータで確認すると両方とも同様な結果になります。

おわりに

今回はアクセス修飾子付き@IBOutletを間接的に接続する方法を纏めました。
Warning&Errorの無限ループは本当に辛かったです。正直に言うともうアプリ開発したくないと感じました😅
でもプログラミングの面白さの一つでもあるので、これからも頑張ります😄
同様な問題を抱えている方がいれば、参考にして頂けれると幸いです。

参考文献

Private IBOutlets Swift - Stack Overflow

この記事が気に入ったらサポートをしてみませんか?