#032 訂飲料App — Part2畫面

功能介紹

  • 查看飲料清單
  • 飲料介紹
  • 選擇甜度、冰塊及配料
  • 加入訂單後可編輯杯數或刪除
  • 編輯訂購人名稱
  • 送出訂單上傳至AirTable

畫面建立

第一篇介紹資料建立傳送門

Part2 畫面建立

實現 Banner 滑動並連動 page control

scroll view AutoLayout 設定調整非常久,分享一下最後調整好的設定

scroll view 的 leading and trailing 與 safe area 間距為 0 (aka裝置與畫面等寬),banner 顯示依照 16:9 的比例,因此 scroll view 的寬高比也一樣設定 16:9,將 3 個 imageView 放進 scrollview 後用 stack view (horizontal) 裝起來,讓三張照片橫向並排,設定如下圖,imageView 也要記得設定寬高比 16:9,接下來把 stack view 的上下左右及高度都等於 superview 的後,就不會報錯了!

技術的部分要實現scroll連動小圓點及滑小圓點連動圖片

將 controller 設為 scroll view 的 delegate ,程式 extension 內加入 scrollViewDidEndDecelerating 當 scroll view 減速停止的時候開始執行

Contentoffset: 賦予現在所在的位置

記得將 page control 跟 scroll view 都拉 outlet

extension MainViewController: UIScrollViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let page = Int(scrollView.contentOffset.x / scrollView.bounds.width)
bannerPageControl.currentPage = page
}
}

@IBAction func changePageControl(_ sender: UIPageControl) {
let xPosition = CGFloat(sender.currentPage) * bannerScrollView.bounds.width
let point = CGPoint(x: xPosition, y: 0.0)
bannerScrollView.setContentOffset(point, animated: true)
}

參考文章

✨客製化Tab Button

  • 設定一個 class 屬性為 UIView
  • 將選定顏色與預設顏色存進變數內,再建立一個 selectorView 就是 button 下方會滑動的長方形色塊
  • func drawSelectorView 用畫面寬度與按鈕數來畫出色塊在畫面上的位置
  • func setTabTitles 將種類的名稱丟進 button 中,依序顯示
  • func tabBtnUI 設定點擊 btn 後 selectorView 會滑動到的位置
class CustomSegmentedControl: UIView {

var selectorColor = UIColor(red: 56/255, green: 109/255, blue: 105/255, alpha: 1)
var defaultColor = UIColor(red: 234/255, green: 165/255, blue: 36/255, alpha: 1)
var selectorView: UIView!

func setTabTitles(titles: [String], btns: [UIButton]){
for (buttonIndex, btn) in btns.enumerated() {
btn.setTitle(titles[buttonIndex], for: .normal)
btn.setTitleColor(defaultColor, for: .normal)
}
btns[0].setTitleColor(selectorColor, for: .normal)
}

func tabBtnUI(index: Int, btns: [UIButton], deviceWidth: CGFloat){
for (_, btn) in btns.enumerated() {
btn.setTitleColor(defaultColor, for: .normal)
}
btns[index].setTitleColor(selectorColor, for: .normal)
let btnwidth = Int(deviceWidth) / btns.count
let selectorWidth = 24
let center = (btnwidth - selectorWidth) / 2
let selectorPosition = CGFloat(btnwidth * index + center)
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.3, delay: 0) {
self.selectorView.frame.origin.x = selectorPosition
}
}

func drawSelectorView(btns: [UIButton], deviceWidth: CGFloat, y: Int, superView: UIView){
let btnwidth = Int(deviceWidth) / btns.count
let width = 24
let center = (btnwidth - width) / 2
let selectorHeight = 4
selectorView = UIView(frame: CGRect(x: center, y: y + selectorHeight, width: width, height: selectorHeight))
selectorView.backgroundColor = selectorColor
view.addSubview(selectorView)
}
}

storyboard 畫面上,將三個按鈕建置 outlet collections ,記得按鈕設定為default style,將三個按鈕依序拉至同一個 IBAction 如下 ,選取後因為對應不同 tab 下方菜單的顯示資料會不同,要 reloadData()

@IBOutlet var segmentTabs: [UIButton]!
@IBOutlet weak var tabBtnStackView: UIStackView!

//viewDidLoad中繪製selector & 丟title名稱進去
customSegmentedControl.drawSelectorView(btns: segmentTabs, deviceWidth: view.bounds.width, y: Int(tabBtnStackView.frame.height), superView: tabBtnStackView)
customSegmentedControl.setTabTitles(titles: titles, btns: segmentTabs)

//點擊按鈕觸發
@IBAction func tabSedgmentBtn(_ sender: UIButton) {
if let num = segmentTabs.firstIndex(of: sender){
tabNumber = num
customSegmentedControl.tabBtnUI(index: num, btns: segmentTabs, deviceWidth: view.bounds.width)
menuCollectionView.reloadData()
}
}

參考文章

Collection View 放置飲料菜單圖片

建立Collection View參考文章

加入預設圖片

執行App時,遇到網路速度不佳或是圖片檔案很大的狀況,loading 中畫面如果一直顯示白色會造成使用者誤會,這時候可以加入預設照片,讓使用者知道目前是loading中,不是發生當機之類的錯誤,也能避免切換 tab button 時,cell 重複利用而顯示上個 tab 同位置照片的問題

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = menuCollectionView.dequeueReusableCell(withReuseIdentifier: "\(FirstTabCollectionViewCell.self)", for: indexPath) as! FirstTabCollectionViewCell
cell.drinkImage.image = UIImage(named: "placeHolder")
MenuController.shared.getImage(url: drink.image[0].url) { image in
DispatchQueue.main.async {
cell.drinkImage.image = image
}
}
}

在建立 cell configuration 的方法中,先將 cell 內的 image 指定為預設照片,後面才從 api 抓到圖片後,將從網路抓下來的圖片指派給 cell 中的 image

網路抓圖 Cache 避免一直下載圖片及顯示時閃一下的不舒服感

雖然已經設定預設圖片,但每次切換 tab 圖片都會重新下載,很浪費傳輸量外,體驗上也不好,就來看看怎麼讓圖片能夠暫存

在 MenuController 中新增 imageCache,NSCache()中要傳入兩個物件,在原本 getImage 方法中加入imageCached = imageCache.object(forKey: imageUrl as NSURL)

如果imageCached中有存過照片就直接讀取,如果沒有才進到URLSession從網路抓抓

class MenuController {
static let shared = MenuController()
//暫存圖片
var imageCache = NSCache<NSURL, UIImage>()
func getImage(url: URL?, completion: @escaping (UIImage?)->Void) {
if let imageUrl = url {
if let imageCached = imageCache.object(forKey: imageUrl as NSURL) {
completion(imageCached)
return
}
URLSession.shared.dataTask(with: imageUrl) { data, urLResponse, error in
if let data, let image = UIImage(data: data) {
self.imageCache.setObject(image, forKey: imageUrl as NSURL)
completion(image)
}else {
completion(nil)
}
}.resume()
}
}
.....
}

參考文章

調整飲料說明文字間距

平常在設計軟體中調整字距(kerning)非常簡單,來看看程式要怎麼調整吧!

飲料的品名與說明文字想調整他的字距,讓整體看起來更符合八曜的風格

if let name = drink?.name,
let description = drink?.description {
let attributedName = NSMutableAttributedString(string: name)
let attributedDescrip = NSMutableAttributedString(string: description)
attributedName.addAttribute(NSAttributedString.Key.kern, value: 2, range: NSMakeRange(0, name.count))
attributedDescrip.addAttribute(NSAttributedString.Key.kern, value: 4, range: NSMakeRange(0, description.count))
namelabel.attributedText = attributedName
descriptionLabel.attributedText = attributedDescrip
}

NSMutableAttributedString 參考文章

下一篇介紹 TableViewCell 中呼叫 UIPicker 跟填寫textField 時不被 Keyboard 遮住,這兩個大魔王!

GitHub

--

--