④④使用圖片,文字,emoji,邊框 & 圓角製作可愛吃貨卡🍖

在 playground 進行

Min
彼得潘的 Swift iOS / Flutter App 開發教室
18 min readAug 22, 2023

--

一直覺得這一題很有趣,但我卻遲遲不敢開始做。因為同學跟學長姊們的設計都很有質感,但我美術細胞實在是 0 … 不過看在今天是七夕的份上,幫有廣大迷妹的貪吃鬼 66 做張吃貨卡吧!

饞死了~

創建存放照片的 ImageView

新建一個 playground 檔案後,先將照片放進 Resources。

創建一個 dogImageView

import UIKit

let dogImageView = UIImageView(image: UIImage(named: "66與口水.png"))

大亂練:使用 UIView 的屬性調整元件基本特性

UIImage 圖片相關-

frame 設定大小位置

dogImageView.frame = CGRect(x: 0, y: 0, width: 375, height: 500)

如果寬高沒有跟原圖差不多,會造成圖片被擠壓

如果不清楚比例,也怕在設定的時候變形,使用屬性 contentMode 設定

contentMode

在練習 storyboard 的時候,使用所有 contentMode 做過比較,如果不清楚它怎麼改變照片顯示的樣子,可以先看這篇:

scaleAspectFit 維持比例,但不會填滿畫面

// ImageView 改為正方形,以測試 contentMode 的效果
dogImageView.frame = CGRect(x: 0, y: 0, width: 375, height: 375)

dogImageView.contentMode = .scaleAspectFit

scaleAspectFill 維持比例填滿 frame 畫面,但超出畫面的會被截掉

dogImageView.contentMode = .scaleAspectFill
頭跟腳被截到了

不能用:scaleToFill 填滿畫面,但比例會跑掉(看起來預設 Image 就是 scaleToFill)

dogImageView.contentMode = .scaleToFill
變胖了

透明度 alpha

範圍是 0–1。數字越小越透明

// frame 回到原來寬高
dogImageView.frame = CGRect(x: 0, y: 0, width: 375, height: 500)
// 透明度
dogImageView.alpha = 0.3
0.3 快要變不見
dogImageView.alpha = 0.5
0.5 明顯一點

UILabel 文字相關-

text 文字

// 設定儲存文字的常數為 UILabel 類別,並設定位置大小
let messageLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 150, height: 30))
// 存入文字
messageLabel.text = "等肉條等到睡著"

textColor 文字顏色

messageLabel.textColor = UIColor(red: 102/255, green: 102/255, blue: 1, alpha: 1)

font 文字大小

messageLabel.font = UIFont.systemFont(ofSize: 12)

CALayer 控制邊框寬度、顏色與圓角

borderWidth 邊框寬度

dogImageView.layer.borderWidth = 10
// 因為layer.borderWidth 沒辦法直接預覽,再呼叫一次 dogImageView 看結果
dogImageView

borderColor 邊框顏色

深色模式的背景讓黑色邊框很不明顯,換一下顏色試試:

dogImageView.layer.borderColor = CGColor(red: 210/255, green: 105/255, blue: 30/255, alpha: 1)
dogImageView

圓角邊框 cornerRadius

dogImageView.layer.cornerRadius = 50

結果照片沒有一起圓角,凸出來了。要將 clipsToBounds 設為 True 才行:

dogImageView.clipsToBounds = true

加入 emoji

// 先把狗狗透明度改回不透明,不然看起來好昏暗
dogImageView.alpha = 1

let meatOnBone = UILabel(frame: CGRect(x: 50, y: 420, width: 50, height: 50))
meatOnBone.text = "🍖"
meatOnBone.font = UIFont.systemFont(ofSize: 50)
// 把骨頭肉加到狗狗圖片裡,成為狗狗圖片的 subview
dogImageView.addSubview(meatOnBone)
看著骨頭肉卻不能吃,好殘酷阿!!

旋轉

transform + CGAffineTransform + rotationAngle

將文字旋轉在之前畫巴西國旗上面的葡文時有做過:

.pi 代表 180 度,1 度是 .pi/180,因此 90 度是 .pi / 180 * 90。

meatOnBone.transform = CGAffineTransform(rotationAngle: .pi / 180 * 90)
// 呼叫圖片看預覽
dogImageView
可以看到骨頭肉轉了 90 度的方向,準備落跑了

這次練習讓骨頭肉有更多動作與變化,參考文章:

縮放

transform + CGAffineTransform + scale

// X 與 Y 寬高兩方向同時放大 2 倍
meatOnBone.transform = CGAffineTransform(scaleX: 2, y: 2)
dogImageView
阿…太香了阿…什麼時候可以吃阿…(設計對白)

旋轉 + 縮放

如果要對骨頭肉同時做旋轉與縮放的動作,不能直接用前方的程式串接,圖片只會顯示最後一次對 transform 的更改。我們有兩個方式可以串接:

方法一

meatOnBone.transform = CGAffineTransform(rotationAngle: .pi / 180 * 90)
meatOnBone.transform = meatOnBone.transform.scaledBy(x: 2, y: 2)
dogImageView

第一種方式是先用 CGAffineTransform 設定旋轉角度之後,再使用 scaledBy 更改旋轉角度。

scaledBy 本名其實是 CGAffineTransformScale,他是一個 instance method。用來在一個已存在的 CGAffineTransform(仿射變換)上,不修改原始的仿射變換,而返回一個新的變換。

方法二

meatOnBone.transform = meatOnBone.transform.concatenating(CGAffineTransform(rotationAngle: .pi / 180 * 90).scaledBy(x: 2, y: 2))
dogImageView

第二種方式更簡潔,只要一行程式。concatenating 的本名是 CGAffineTransformConcat,它用來將兩個仿射變換組合在一起,形成一個新的變換。

成果

骨頭肉既變大了,又轉了方向準備逃跑了

回到沒有任何效果的 CGAffineTransform

骨頭肉會回到原本的大小與方向,再做其他變化。

meatOnBone.transform = CGAffineTransform.identity

mirror 鏡像翻轉

我們再創建一塊骨頭肉,用來實驗鏡像翻轉。

// 在骨頭肉同樣的位置創建一個 mirrorMeatOnBone,設定同骨頭肉
let mirrorMeatOnBone = UILabel(frame: CGRect(x: 50, y: 420, width: 50, height: 50))
mirrorMeatOnBone.text = "🍖"
mirrorMeatOnBone.font = UIFont.systemFont(ofSize: 50)
// 將鏡像骨頭肉的左上 x 座標值設在骨頭肉最右邊 x 座標值的右邊 10 單位
mirrorMeatOnBone.frame.origin.x = meatOnBone.frame.maxX + 10
// 將鏡像骨頭肉 x 軸的值變成 -1 倍,會變成鏡像
mirrorMeatOnBone.transform = CGAffineTransform(scaleX: -1, y: 1)
// 將鏡像骨頭肉加回狗狗圖裡
dogImageView.addSubview(mirrorMeatOnBone)
dogImageView
變兩個惹~~~

y 軸也可以翻轉,只是注意 y 軸方向越下方數字越大。將剛剛 x 軸鏡像翻轉的那兩行程式修改一下,變成垂直翻轉:

// 因為原本骨頭肉最高點是他 y 軸的最小值,因此 maxX 要變成 minY
mirrorMeatOnBone.frame.origin.y = meatOnBone.frame.minY - 50
// 因為要改成垂直翻轉,所以是改變 y 軸變成 -1
mirrorMeatOnBone.transform = CGAffineTransform(scaleX: 1, y: -1)

translation 位移

骨頭肉真的要逃走了,meatOnBone 透過 transform + CGAffineTransform + init(translationX:y:),調整 x 與 y 軸移動的單位。

meatOnBone.transform = CGAffineTransform(translationX: -35, y: 25)
可以看到原本的骨頭肉往左下角移動

結合縮放、旋轉與位移

最後當然什麼都要加一加,我們之前結合過縮放與旋轉,現在再試著加上位移。那因為這三個變換都是數學的運算,因此前後順序會影響到結果。尤其是先位移再縮放跟先縮放再位移會是不一樣的。

先回到最初的一根骨頭肉

方法一

// identity 將之前變化的復原,接著執行平移、縮放與旋轉
meatOnBone.transform = CGAffineTransform.identity.translatedBy(x: -35, y: 25).scaledBy(x: 0.5, y: 0.5).rotated(by: .pi / 180 * 135)
可以看到骨頭變小跑遠又轉向了

方法二

與前面的方法二相同,先定義三種屬性,再使用 concatenating 一個一個串起來。注意先後順序與方法一相反。這樣出來的樣式是一樣的:

let translateTransform = CGAffineTransform(translationX: -35, y: 25)
let scaleTransform = CGAffineTransform(scaleX: 0.5, y: 0.5)
let rotateTransform = CGAffineTransform(rotationAngle: .pi/180 * 135)
meatOnBone.transform = rotateTransform.concatenating(scaleTransform).concatenating(translateTransform)
dogImageView

上述是各種可使用的方法。

最終的吃貨卡:

不知道使用吃貨卡可以招喚出什麼…

程式:

import UIKit

let dogImageView = UIImageView(image: UIImage(named: "66與口水.png"))
dogImageView.frame = CGRect(x: 0, y: 0, width: 375, height: 500)
var messageLabel = UILabel(frame: CGRect(x: 160, y: 280, width: 200, height: 50))
messageLabel.text = "吃貨卡"
messageLabel.textColor = UIColor(red: 210/255, green: 105/255, blue: 30/255, alpha: 1)
//文字使用系統粗體
messageLabel.font = UIFont.boldSystemFont(ofSize: 50)
dogImageView.addSubview(messageLabel)
//設定邊框
dogImageView.layer.borderWidth = 10
dogImageView.layer.borderColor = CGColor(red: 210/255, green: 105/255, blue: 30/255, alpha: 1)
dogImageView.layer.cornerRadius = 50
dogImageView.clipsToBounds = true

var meatOnBone = UILabel(frame: CGRect(x: 52, y: 420, width: 50, height: 50))
meatOnBone.text = "🍖"
meatOnBone.font = UIFont.systemFont(ofSize: 30)
dogImageView.addSubview(meatOnBone)
meatOnBone.transform = CGAffineTransform(scaleX: 2, y: 2)

meatOnBone = UILabel(frame: CGRect(x: 52, y: 370, width: 50, height: 50))
meatOnBone.text = "🍖"
meatOnBone.font = UIFont.systemFont(ofSize: 30)
dogImageView.addSubview(meatOnBone)
meatOnBone.transform = CGAffineTransform(scaleX: 2, y: 2)

meatOnBone = UILabel(frame: CGRect(x: 52, y: 320, width: 50, height: 50))
meatOnBone.text = "🍖"
meatOnBone.font = UIFont.systemFont(ofSize: 30)
dogImageView.addSubview(meatOnBone)
meatOnBone.transform = CGAffineTransform(scaleX: 2, y: 2)

meatOnBone = UILabel(frame: CGRect(x: 52, y: 270, width: 50, height: 50))
meatOnBone.text = "🍖"
meatOnBone.font = UIFont.systemFont(ofSize: 30)
dogImageView.addSubview(meatOnBone)
meatOnBone.transform = CGAffineTransform(scaleX: 2, y: 2)

// 對角鏡像骨頭肉們
var mirrorMeatOnBone = UILabel(frame: CGRect(x: 278, y: 30, width: 50, height: 50))
mirrorMeatOnBone.text = "🍖"
mirrorMeatOnBone.font = UIFont.systemFont(ofSize: 30)
// 將鏡像骨頭肉 x 軸跟 y 軸的值變成 -2 倍,會變成對角鏡像(2因為前面的骨頭肉都是兩倍大小)
mirrorMeatOnBone.transform = CGAffineTransform(scaleX: -2, y: -2)
// 將鏡像骨頭肉加回狗狗圖裡
dogImageView.addSubview(mirrorMeatOnBone)

mirrorMeatOnBone = UILabel(frame: CGRect(x: 278, y: 80, width: 50, height: 50))
mirrorMeatOnBone.text = "🍖"
mirrorMeatOnBone.font = UIFont.systemFont(ofSize: 30)
mirrorMeatOnBone.transform = CGAffineTransform(scaleX: -2, y: -2)
dogImageView.addSubview(mirrorMeatOnBone)

mirrorMeatOnBone = UILabel(frame: CGRect(x: 278, y: 130, width: 50, height: 50))
mirrorMeatOnBone.text = "🍖"
mirrorMeatOnBone.font = UIFont.systemFont(ofSize: 30)
mirrorMeatOnBone.transform = CGAffineTransform(scaleX: -2, y: -2)
dogImageView.addSubview(mirrorMeatOnBone)

mirrorMeatOnBone = UILabel(frame: CGRect(x: 278, y: 180, width: 50, height: 50))
mirrorMeatOnBone.text = "🍖"
mirrorMeatOnBone.font = UIFont.systemFont(ofSize: 30)
mirrorMeatOnBone.transform = CGAffineTransform(scaleX: -2, y: -2)
dogImageView.addSubview(mirrorMeatOnBone)

dogImageView

Reference:

--

--