owned this note
owned this note
Published
Linked with GitHub
# Discussion
## 目前正在做的作業
```swift=
class ViewController: UIViewController {
@IBOutlet weak var firstRoadLabel: UILabel!
@IBOutlet weak var firstSpeedLimitLabel: UILabel!
@IBOutlet weak var secondRoadLabel: UILabel!
@IBOutlet weak var seconSpeedLimit: UILabel!
@IBOutlet weak var thirdRoadLabel: UILabel!
@IBOutlet weak var thirdSpeedLimit: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let task1 = DispatchQueue(label: "getFirstData")
let task2 = DispatchQueue(label: "getSecondData")
let task3 = DispatchQueue(label: "getThirdData")
let group = DispatchGroup()
group.enter()
task1.async(group: group, qos: .default) { [self] in
print("----task1 start----")
getData(offset: "0")
print("----task1 end----")
group.leave()
}
group.wait()
group.enter()
task2.async(group: group, qos: .default) { [self] in
print("----task2 start----")
let data = getData(offset: "10")
print("----task2 end----")
group.leave()
}
group.wait()
group.enter()
task3.async(group: group, qos: .default) { [self] in
print("----task3 start----")
let data = getData(offset: "20")
print("----task3 end----")
group.leave()
}
}
func getData(scope: String = "resourceAquire", rid: String = "5012e8ba-5ace-4821-8482-ee07c147fd0a", limit: String = "1", offset: String) {
guard let url = URL(string: "https://data.taipei/opendata/datalist/apiAccess?scope=\(scope)&rid=\(rid)&limit=\(limit)&offset=\(offset)") else { return }
var request = URLRequest(url: url)
request.httpMethod = "Get"
let session = URLSession(configuration: .default)
let task = session.dataTask(with: request) {
(data, response, error) in
guard let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data else { return }
do {
let dataResponse = try JSONDecoder().decode(Data.self, from: data)
DispatchQueue.main.async { [self] in //這裡應該要改,但不確定要怎麼寫
switch offset {
case "0":
configFirstLabels(dataResponse: dataResponse)
case "10":
configSecondLabels(dataResponse: dataResponse)
case "20":
configThirdLabels(dataResponse: dataResponse)
default:
print("This is the data you want: \(dataResponse)")
}
}
} catch {
print(error)
}
}
task.resume()
}
func configFirstLabels(dataResponse: Data) {
firstRoadLabel.text = dataResponse.result.results.first?.設置路段
firstSpeedLimitLabel.text = dataResponse.result.results.first?.速限
}
}
struct Data: Codable {
let result: DataResult
}
struct DataResult: Codable {
let limit, offset: Int
let results: [ResultElement]
}
struct ResultElement: Codable {
let road, speedLimit: String
enum CodingKeys: String, CodingKey {
case road = "設置路段"
case speedLimit = "速限"
}
}
```
## 已解決
### 多個實體共用delegate
```swift=
>extension ViewController: SelectionDataSource {
func numberOfItems(_ selectionView: SelectionView) -> Int {
方法一
switch selectionView {
case is topSelectionView:
return topSource.count
case is bottomSelectionView:
return bottomSource.count
default:
return 0
}
}
> func modelForItem(_ selectionView: SelectionView, at index: Int) -> ButtonModel {
方法二
switch selectionView.tag {
case 0:
return topSource[index]
case 1:
return bottomSource[index]
default:
return .init()
}
}
}
```
==A:「delegate方法要把自己傳進去」因為食物上就是會發生同一個人操作兩個同型別的實體 所以他故意把題目設計成上下兩個==
==A:你如果要辨認就是要仰賴這個實體是不是你所想的那一個==
### 回到流程首頁 v.s. 到tabbar的某頁
```swift=
//回到流程首頁
@objc func goHomeVC(sender: UIButton) {
self.navigationController?.popToRootViewController(animated: true)
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
//到tabbar的某頁
@objc func goHomeVC(sender: UIButton) {
self.tabBarController?.selectedIndex = 0 //push to homeVC
self.navigationController?.popToRootViewController(animated: true) //when user selects cart of tabbar item, app shows cartVC
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
```
### CoreData + Singletion design pattern
- `Singleton Design Pattern` 可以讓資料被app全域使用,但是只能透過一個入口取得(static宣告class實體/ private宣告取用入口實體 / private init初始化class實體)
- `CoreData` 封裝資料,可以新增.set / .insert、取得.retrieve、更新.update、刪除.delete
- key/value的形式存data
- `Notification Center`:物件可以去向通知中心註冊,成為某一種通知事件的觀察者,然後當有人向通知中心送出通知的時候,通知中心就會去找它的註冊表裡面,所有有註冊這個通知類型的觀察者,並將通知傳送給它們
- `Local SQLite Database`:`coreData`存放在local的位置
```swift=
import CoreData
//設計模式1: 泛型工廠方法(靜態func產生實體)
class StorageManager {
private let context = .......viewContext
func create<T: NSManagedObject>(_ objType: T.Type) -> T {
return T(context: context)
}
}
// 如果用了設計模式1當你要建立物件要這樣做
let cartItem = StorageManager.shared.create(CartItem.self)
cartItem.size = "S"
cartItem.coloeCode = XXX
cartItrm.amout = 987654321
StorageManager.shared.save() // 這邊不知道你會怎麼寫
//設計模式2: Closure處理方法
class StorageManager {
//裡頭包CoreData
static let shared = StorageManager()
private init() {} //prevent from creating another instance
//connect StorageManager to AppDelegate
private let appDelegate = UIApplication.shared.delegate as! AppDelegate
//connect StorageManager to the context in AppDelegate
private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func process(block: (_: NSManagedObjectContext) -> Void) {
block(context)
if context.hasChanges {
try? context.save()
}
}
}
// 如果用了設計模式2當你要建立物件要這樣做 (以insert為例)
StorageManager.shared.process { context in
let cartItem = CartItem(context: context)
cartItem.size = "S"
cartItem.coloeCode = XXX
cartItrm.amout = 987654321
}
//單用process closure,可以做的事情不侷限CRUD什麼都行,反正就是做完幫他呼叫一次save
```
### segue 替代方案
沒有StoryBoard,要怎麼做到以下的1-3行

```swift=
let controller = GamesTableViewController()
controller.gamesArray = dataStore.allGames.summer
```
### 動畫由下而上升起
```swift=
let xxxContraint = NSLayoutConstraint(item: <#T##Any#>, attribute: <#T##NSLayoutConstraint.Attribute#>, relatedBy: <#T##NSLayoutConstraint.Relation#>, toItem: <#T##Any?#>, attribute: <#T##NSLayoutConstraint.Attribute#>, multiplier: <#T##CGFloat#>, constant: <#T##CGFloat#>)
xxxContraint.constant = 20
UIView.animated {
view.superview.layoutIfNeeded()
}
//更新的constraint要寫在動畫外面,讓他有時間去重新計算所有view 裡的constraints
```
### 建構
```swift=
//建構物件時選擇使用哪個init,他就會從那個入口進來
//UIKit系列
override init(frame: CGRect) { // Designated
super.init(frame: frame) //既然要覆寫又要建構 妳就會使用super呼叫自己父類別的建構
}
required init?(coder aDecoder: NSCoder) { // 不用SB就不用理他 或著 fatalError改super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
// 如果要自己帶東西進來
convenience init(color: UIColor) {
self.init(frame: .zero) // 或是忽略.zero 因為frame的預設值就是.zero 所以就會寫成 self.init()
}
```

### 帶資料進新的view
1. 在AddToCartView寫一個公開func addProducts(_ products: [Product]) { //加入以後要做的事情 }
2.
```swift=
let nextVC = DetailViewController()
nextVC.modalPrentationStyle = .選你想要的 ex.支付密碼是 .overCurrentContext/.overFullScreen
nextVC.products = products
present(nextVC, animated: true)
或者用convenience init的做法 就跟DetailViewController意思一樣
let nextVC = CartViewController(products: myPickedProducts)
nextVC.modalPrentationStyle = .overCurrentContext
present(nextVC, animated: true)
```
### 帶資料進新的VC
> 概念:寫一個自己想要的建構方法 依照你自己的喜好對VC初始化
補充:private是存取修飾 意味著我不想給class以外的任何人取用 所以當他要給的時候 只能透過我們寫的init
```swift=
//原本的VC (HomeViewController) 的UITableViewDataSource裡,HomeVC打api拿到資料了
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedProduct = hots[indexPath.section].products[indexPath.row]
let detailVC = DetailViewController(product: selectedProduct) //product 只是argument label,可以任意取名字,只要後續在新的VC呼叫時用一樣的就好
self.navigationController?.pushViewController(detailVC, animated:true)
}
```
```swift=
//新的VC (DetailViewController)
class DetailViewController: UIViewController {
convenience init(product: Product) { //product跟上述地方要用相同的argument label,另外Product是型別
self.init()
self.productData = product //有容器接住資料
}
private var productData: Product! //宣告容器
override func viewDidLoad() {
super.viewDidLoad()
這邊就可以直接拿productData來用了,也可以塞到cell裡去了
因為你在建構的時候把整個Product物件丟進來這個VC
因為對於Home而言 selectedProduct不重要
對DetailViewController才有意義
}
}
```
```swift=36
//Usage:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let detailCell = tableView.dequeueReusableCell(withIdentifier: "\(DetailCell.self)", for: indexPath) as! DetailCell
detailCell.detailLabel.text = productData.texture
}
```
### 怎麼做到點cell後換頁
`self.navigationController?.pushViewController(detailViewController, animated:true)`
### 切換tab後,回到最上方
```swift=
//Class ViewController scope 有個按按鈕時會觸發的func,其中會清除producslist array
func reset(selectedTab: Catalog) {
self.selected = selectedTab
productsPage = 0
productManager.getProducts(selectedTab: selectedTab, page: productsPage!)
productslist = []
collectionView.es.resetNoMoreData()
}
```
```swift=
extension CatalogViewController: ProductManagerDelegate {
func manager(_ manager: ProductManager, didGet products: ProductData, selectedTab catalog: Catalog) {
//判斷producslist array是不是空的,如果是的話代表是按完按鈕的情境
let shouldScrollToTop = productslist.isEmpty
productslist += products.data
productsPage = products.nextPage
collectionView.reloadData()
//確定reload完data以後,讓畫面回到最上方(如果太早回最上方,會找不到collection view,會crash)
if shouldScrollToTop {
collectionView.setContentOffset(.zero, animated: true)
}
}
func manager(_ manager: ProductManager, didFailWith error: Error) {
print(error)
}
}
```
### tableView和navigationbar沒有距離
safe area有關嗎?
==你的tableView沒有用Constraints-Layout,然後如果top想要接到navigationbar的bottom,妳可以接view.safeAreaLayoutGuide.topAnchor==
^^^^^ It works, thanks a loooooooot
-----------
### Anchor
啊我是覺得Anchor寫法跟snapkit比較像也比較短啦
貼一下Wii的大作
```swift=
let stackView = UIStackView(arrangedSubviews: [textField, label])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.alignment = .fill
stackView.spacing = 4
addSubview(stackView)
label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20)
])
```
```swift=
// SnapKit 等效比對
addSubview(stackView)
stackView.snp.makeConstraints { make in
make.leading.equalTo(self.snp.leading)
make.top.equalTo(self.snp.top)
make.bottom.equalTo(self.snp.bottom)
make.trailing.equalTo(self.snp.trailing).offset(-20)
}
如果兩個Anchor是同一個邊,後面那一個可以不用寫
make.bottom.equalTo(self.snp.bottom)
相等於
make.bottom.equalTo(self)
另外因為此例中self即為superview
所以
make.bottom.equalTo(self)
可以寫成
make.bottom.equalToSuperview()
```
### 移除storyboard
設定:
1. https://medium.com/@Chien_Cheng/%E7%82%BA%E4%BB%80%E9%BA%BC%E4%B8%8D%E4%BD%BF%E7%94%A8-stordboard-813053e3692d
2. https://medium.com/swift-productions/ios-start-a-project-without-storyboard-xcode-12-253d785af5e7
```swift=
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 俄羅斯娃娃
let homeVC = HomeViewController()
let homeNavC = UINavigationController(rootViewController: homeVC)
homeNavC.navigationItem.titleView = UIImageView(image: UIImage(named: "Image_Logo02.png")!)
homeNavC.tabBarItem = // 製作屬於他的 UITabBarItem
let tabBarVC = UITabBarController()
tabBarVC.setViewControllers([homeNavC, .init(), .init(), .init()], animated: false)
window = UIWindow(frame: UIScreen.main.bounds)
window!.backgroundColor = .white
window!.makeKeyAndVisible()
window!.rootViewController = tabBarVC
return true
}
```