=======================================================
iOS開發已經做了快4年了,聽說Swift也已經有兩年多,但是一直都只是把學習停留在表面。無意中聽說了有一個叫Sam Lu在Twitter上發起了一個100天做40個Swift小程式的活動,再加上國內看到了Allen_朝輝寫的Swift學習的文章,心裡暗自下了一個決定:30天寫30個Swift小程式,希望能推動自己學習Swift的計劃。這30個小程式難度不同,有的一個晚上就能寫完,有的要佔用週末大部分時間來細研究。大部分不會的東西Google都能找到,就算Swift版本沒有找到Objective-C版本然後用Swift重寫就好,好在他們對應關係比較明確。
用例方面,既參考了Sam Lu的40個小專案,也參考了Allen_朝輝的專案,還有的是我自己仿寫的知名App。
其實我並不是唯一在國內發起這個30天30個Swift小程式並且將其開源的作者,但是我可能是唯一一個從頭到尾用XCode 8 + Swift3環境編寫的作者。而且,為了讓程式碼更加可讀,所有程式碼完全手寫,而非用Storyboard(除了只能用Storyboard的,例如apple watch app)。實際上多人協作的專案中我們儘可能少用Storyboard,因為很容易出現衝突問題。況且從學習的角度,storyboard很難說清楚操作步驟是什麼。在這上面我其實花了不少時間,但是我認為很值得。
希望能有更多對Swift感興趣的開發者加入這項#30天30個Swift小程式 的活動裡面來。以下為Github連結: github.com/nimomeng/30…
Project 30 - Google Now App
我學到了
- 這次Project演示了Present/Dismissd如何做Transition動畫,這和做Push/Pop的轉場動畫的基本原理都是一樣的
- 這次的動畫參考了BubbleTransition的動畫效果,在它之上加了修改,支援傳入自定義的UI屬性,方便做組合型動畫(例如本例中按鈕不僅放大而且上下移動)
- 動畫變化的原理是將相應的ViewController進行Scale變換,再通過一個Bubble的蒙版看起來像是氣泡效果
- 其它的細節知識如下:
- 畫圓形按鈕的方法,必須要cornerRadius屬性為邊長的1/2,具體程式碼如下:
triggerButton.layer.cornerRadius = triggerButton.frame.width / 2
triggerButton.layer.masksToBounds = true
複製程式碼
- UITextField設定邊框樣式,必須要將其style設定為none才可以
- 參考專案:BubbleTransition
Project 29 - Beauty Contest
我學到了
- 這個專案是基於Yalantis的Koloda來製作的。 Koloda是一個非常好用的UIImage選擇器
- Swift中的懶載入的使用方法:
- 兩種方式:
lazy var firstWay = "first"
複製程式碼
以及
lazy var secondWay: String = {return "Second"}()
複製程式碼
注意:第二種方式要注意定義好欄位型別,以便於編譯時的型別檢查;以及不要忘記最後的小括號
- 為什麼要用Lazy:因為這裡面需要先知道KolodaView的尺寸,才能定Overlay的尺寸。因此這裡有一個依賴關係,因此用懶載入最合適。
- Swift中的unowned和weak的區別:
- unowned更像OC裡的unsafe_unretained; weak還是那個weak。
- 如果確定使用時一定不會被釋放,可以用unowned;否則最好用weak
Project 28 - SnapChat Like App
我學到了
-
UIScrollView的基本使用和細節小點,例如禁止彈跳的bounces屬性,整頁切換的isPagingEnabled屬性,起始位置contentOffset屬性等
-
載入子Viewcontroller的addChildViewController方法
-
"xxx class has no initializers"問題:
You have to use implicitly unwrapped optionals so that Swift can cope with circular dependencies (parent <-> child of the UI components in this case) during the initialization phase. @IBOutlet var imgBook: UIImageView! @IBOutlet var titleBook: UILabel! @IBOutlet var pageBook: UILabel! 複製程式碼
-
許可權問題,具體錯誤描述為: "This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data." 解決方法:iOS10之後的許可權問題,在info.plist裡新增相應的許可權以及描述即可。 本例中許可權為: NSCameraUsageDescription PhotoMe needs the camera to take photos. NSMicrophoneUsageDescription PhotoMe needs the microphone to record audio with Live Photos. NSPhotoLibraryUsageDescription PhotoMe will save photos in the Photo Library.
-
AVCaptureSession 的使用方法:
- AVCaptureSession是AVFoundation的核心類,用於捕捉視訊和音訊,協調視訊和音訊的輸入和輸出流.
- 建立
AVCaptureSession
例項,並設定其sessionPreset
值,也就是設定畫面的質量。 - 給Session新增Input。一般是Video或者Audio資料,也可以兩者都新增,即AVCaptureSession的輸入源AVCaptureDeviceInput。具體步驟是先獲取對應的
device
例項(此時決定是用Video還是Audio),再由例項獲取其Input Source。最後將input source add到session中。 - 給Session新增Output,即AVCaptureSession的輸出源。一般輸出源分成:音視訊源,圖片源,檔案源等。這裡以靜態圖片的輸出源為例,指的是AVCapturePhotoOutput。最後將其也add到session中。
- 設定預覽圖層,即AVCaptureVideoPreviewLayer。在input,output等重要資訊都新增到session以後,可以用session建立AVCaptureVideoPreviewLayer,這是攝像頭的視訊預覽層。這裡千萬別忘了將Layer新增到View中。
- 啟動Session,即
captureSesssion.startRunning()
-
Photo的捕獲方法
- AVCaptureSession設定成功,並啟動
- 建立
AVCapturePhotoSettings
物件,並配置相應的屬性,例如是否開啟flash,是否開啟防抖模式等等 - 執行輸出源的capture方法,並制定具體的AVCapturePhotoSettings物件以及delegate物件
- 在capture的delegate方法:
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?)
複製程式碼
中執行獲取影象的具體邏輯。本例中是先將buffer轉換為data,再轉換為UIImage,最終write到相簿資料夾中。
- Reference: iOS AVCaptureSession學習筆記
Project 27: Carousel Effect (跑馬燈效果)
我學到了
-
UICollectionView的使用
- 與UItableView的不同在於,每一個對應的Cell(不論是content的cell還是header,footer的),都需要預先執行register方法
- Cell間距太窄的問題,可以通過minimumLineSpacingForSection的DataSource代理方法來解決掉
- 如果選擇的layout為UICollectionViewFlowLayout,可以通過修改scrollDirection屬性來修改滾動方向
-
自定義Layout要在對應的子類裡實現如下方法
prepare() shouldInvalidateLayout(forBoundsChange newBounds: CGRect) targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) layoutAttributesForElements(in rect: CGRect) 複製程式碼
其中:
-
prepare可以定義初始化操作
-
shouldInvalidateLayout,判斷是否需要更新每個Cell的bounds。如果我們的layout是那種每個cell需要動態變化的layout,則設定為true;否則為了效能考慮,請設定為false。預設為flase。
-
targetContentOffset,如果我們需要圖片在滾動的過程中在特定位置可以停下來(類似iphone上專輯圖片的選擇),請在此函式中國年給出停下來的具體規則
-
layoutAttributesForElements 返回所有元素此時的所有佈局。我們會在這裡定義在滾動過程中所有其他元素的attribute佈局相關屬性。例如本例中,離螢幕中間越近,圖片被縮放的越大;離螢幕越小,圖片被縮放的越小。
-
Reference:
- UICollectionView綜合檢視
- [用UICollectionView實現相簿功能] (www.jianshu.com/p/4f691ec73…)
-
Visual Effect View的使用
-
儘量在需要模糊化的圖層之後新增進去,會自動虛化所覆蓋的圖層
let blurEffect: UIBlurEffect = UIBlurEffect(style: .light) let blurView: UIVisualEffectView = UIVisualEffectView(effect: blurEffect) blurView.frame = self.view.bounds bgView.addSubview(blurView) 複製程式碼
-
-
UISegmentedControl 的使用(略)
-
其它:#selector()中的func如果帶有引數,請將具體引數也一起寫進去,例如:
#selector(action_segmentValueChanged(sender:)
這個規則和OC不太一樣,要注意。
Project 26 - Twitter-like Splash
我學到了
- 這個效果嘗試了用簡單的UIView的animation方法會比較吃力,因此轉而使用CAAnimation來做。
- 我們需要的效果,設定keyPath為"bounds"。吐槽一下,蘋果為什麼不做一個列舉。。。完整的keyPath列表如下所示:
- 由於logo的動畫定製化要求比較高,所以關於這個變化的動畫,選擇CAAnimation裡的CAKeyFrameAnimation來做。主要關注keyTimes,values屬性,也就是keyFrame的屬性。timingFunctions屬性是keyframe的count - 1, 也就是frame1到frame2,frame2到frame3的動畫過渡函式。這個不多說了,之前的Project有提到過。
- 在logo變大的過程中,logo中間的alpha值也應該有白色變為透明,因此應該先新增一個maskView,藏在最上層,logo層之下,作為白色的底。動畫trigger的時間和duration與logo的動畫保持步調一致,並且記得在動畫complete的時候被移除掉。這裡使用了CABasicAnimation的animationDidStop代理來完成。
- logo的透明度變化既可以使用簡單的UIView的animation方法來做,也可以採用layer級別的CABasicAnimation來完成。因為對前者比較熟悉了,所以我在這裡使用後者,注意keyPath是
opacity
。程式碼比較簡單,這裡不贅述。 - 整體效果還是很炫的:)
- CAKeyFrameAnimation參考此篇文件
Project 25 Custom Transition
我學到了
- NavigationController的動畫是可以自定義的,去實現UINavigationControllerDelegate裡的方法就好
- 如果切換動畫只需要關注之前的VC和之後的VC,不需要關注中間過程,直接實現以下方法即可:
navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
複製程式碼
- 上述方法的返回值
UIViewControllerAnimatedTransitioning
需要自定義動畫,需要實現UIViewControllerAnimatedTransitioning
代理,實現具體的兩個方法:- 轉場動畫時間,直接返回一個時間即可
transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
複製程式碼
- 轉場動畫過程,如下所示,這個比較複雜:
animateTransition(using transitionContext: UIViewControllerContextTransitioning)
複製程式碼
- 第一步,獲得轉場動畫的fromVC,toVC,container:
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! XXXController
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! YYYController
let container = transitionContext.containerView
複製程式碼
- 之後是動畫前的準備工作,例如image賦值,例如座標的計算:
let snapshotView = fromVC.selectedCell.imageView.snapshotView(afterScreenUpdates: false)
snapshotView?.frame = container.convert(fromVC.selectedCell.imageView.frame, from: fromVC.selectedCell)
....
複製程式碼
- 最後當然是Animation動畫的執行邏輯了,可以通過UIView的animate方法去實現。具體引數和方法可以參考之前的Project來進靈活組合。
- 進入的動畫最後一定不能忘記加上
transitionContext.completeTransition(true)
,說明了讓navigationController來接管控制權利(在completion的block中) - 退出的動畫記得帶上
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
說明動畫執行完成
- 進入的動畫最後一定不能忘記加上
- 如果需要關注動畫的執行過程,則在上述的基礎之上還應該實現下述方法:
navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
複製程式碼
其中,UIViewControllerInteractiveTransitioning
是動畫過渡物件
- 獲取iOS中手從左往右沿螢幕滑動的事件,是通過
UIScreenEdgePanGestureRecognizer
方法並設定其edges為left實現的:
let edgePanGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(edgePanGestrueAction(_:)))
edgePanGesture.edges = UIRectEdge.left
複製程式碼
Project 24 - Vertical Menu Transition
我學到了
- 本文和Google Now App專案思路一致,都是針對Present/Dismiss的操作進行自定義Transition
- 由於動畫需要區域性截圖,因此建議將Present和Dismiss的Transition寫到一起,通過一個變數來進行不同動畫的切換和控制。變數可以通過
animationController(forDismissed dismissed: UIViewController)
與animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController)
來進行設定。 - target-action方式,一般會將target設定為self。如果設定為對應的delegate,則action欄位應該填寫
#selector(CustomTransitionDelegate.functionName)
- 在Present/Dismiss的自定義轉場動畫中,記得在complete回撥中加入動畫結束語句塊:
transitionContext.completeTransition(true)
fromViewController?.endAppearanceTransition()
toViewController?.endAppearanceTransition()
複製程式碼
Project 23 - Side Navigation App
####我學到了
- Swift-OC混編方法
- 新建一個標頭檔案,例如名為Bridge.h
- 單擊Project檔案,選擇Build Setting,找到Objective-C Bridge Header,輸入Bridge.h的路徑
- 之後所有需要在swift檔案中引用的OC檔案的標頭檔案放到Bridge.h中進行import
- 側滑效果借鑑了SWRevealViewController,使用步驟如下(原專案只提到了OC中的呼叫方法)
- 專案中至少有以下幾類viewController:第一頁展示的VC,比如FrontViewController;tabeView所在的MenuViewController
- 在AppDelegate中根據規則建立自定義Window,具體步驟為:
- 建立UIWindow
- 新建兩個UINavigationController,分別以FrontViewController和MenuViewController為rootViewController
- 例項化SWRevealViewController,並設定rearViewController的值和frontViewController的值。其中,rearViewController是tableView所在的UINavigationController,frontViewController是FrontViewController所在的UINavigationController
- 將例項化的SWRevealViewController設定為Window的rootViewController
window = UIWindow(frame: UIScreen.main.bounds)
let rearNavigationController = UINavigationController(rootViewController: MenuViewController())
let frontNavigationController = UINavigationController(rootViewController: FrontViewController())
let revealController = SWRevealViewController(rearViewController: rearNavigationController, frontViewController: frontNavigationController)
revealController?.delegate = self
window?.rootViewController = revealController
window?.makeKeyAndVisible()
複製程式碼
- 需要在每一個ViewController中加入左滑啟用Menu的邏輯,一句話:
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
複製程式碼
- 效果需要,最好隱藏掉status bar以及navigationBar:
self.navigationController?.isNavigationBarHidden = true
複製程式碼
override var prefersStatusBarHidden: Bool { return true }
複製程式碼
Project 22 - Basic Animations
我學到了
- 本次涉及到最基本的UIAnimation,很多複雜的Animation其實是各種簡單的Animation的疊加,所以不能輕視
- Position的Animation既可以通過直接修改frame的origin屬性,也可以直接通過UIView的transform來進行修改
- Opacity直接改Alpha值就可以了
- Scale是修改了UIView的transform,傳入要縮放的相對比例並建立對應的CGAffineTransform物件。例如:
heartView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
複製程式碼
- Color是直接修改backgroundColor就可以了
- Rotation是通過修改UIView的transform,傳入要旋轉的值並建立對應的CGAffineTransform物件。其中,值為0 - 2*Pi之間,表示0到360°之間。注意,正值為逆時針轉動。例如:
self.rollingBallView.transform = CGAffineTransform(6.28)
複製程式碼
Project 21 CoreData App
我學到了
- 一定要勾選 UseCoreData,這樣在Appdelegate裡會自動生成部分程式碼
- 不一定非要通過Editor生成SubClass
- 本例中Entity如圖所示:
- 在需要呼叫CoreData的類中,import CoreData
- 本例比較簡單,只進行了getResult和Add的操作,思路分別為:
- getResult:
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: EntityName)
do {
let searchResults = try getContext().fetch(fetchRequest)
dataSource = searchResults as! [TodoList]
} catch {
// todo error handler
}
複製程式碼
注意,或取出來的searchResult可以直接例項化為TodoList(TodoList是我的Entity名字),這樣後續就可以直接使用TodoList的content方法了。
- saveContent:
let context = getContext()
// 定義一個entity,這個entity一定要在xcdatamodeld中做好定義
let entity = NSEntityDescription.entity(forEntityName: EntityName, in: context)
let todoList = NSManagedObject(entity: entity!, insertInto: context)
todoList.setValue(content, forKey: "content"
do {
try context.save()
}catch{}
複製程式碼
對應getConent方法的程式碼兩行:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
複製程式碼
如此操作後使用的時候直接通過獲取TodoList物件,然後呼叫其content方法即可完成。
cell.textLabel?.text = (dataSource[indexPath.row]).content
- UIAlertController新增輸入款的方法:
alertController.addTextField { (textField) in
textField.placeholder = "Please input the todo Item"}
複製程式碼
- 該方法在XCode8.3 + Swift3.2測試通過,CoreData在iOS10的變化很大,之前的版本可能和上述操作方法有出入
- 參考文章
Project 20 - Apple Watch OS App - Guess Game
我學到了
-
Watch程式,需要在create project的先選擇Watch OS的Section,之後選擇如下:
-
watch中的UI只可以通過Storyboard來進佈局,佈局檔案在WatchKit App中的Interface.storyboard中
-
例子中涉及到了watch和主app的互動,這裡使用的是
WCSession
方法,使用步驟如下:- 確定app所在裝置是否支援WCSession
- 生成一個WCSession物件,並設定其delegate
- 啟用此WCSession物件 至此部分,程式碼為:
let wcsession = WCSession.default()
if WCSession.isSupported() {
wcsession.delegate = self
wcsession.activate()
}
複製程式碼
- 傳送通訊(watch與主app之間)通過WCSession物件的updateApplicationContext方法來進行,例如
try wcsession.updateApplicationContext(["numberToBeGuessed": number])
- 接收方通過代理方法來接收並解析傳送的訊息
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any])
複製程式碼
Project 19 - TodayWidget
我學到了
- 建立Today Widget: File > New > Target…,然後選擇 iOS 中的 Application Extension 的 Today Extension
- 為了方便Widget與App資料共享,需要切換成App Group模式。步驟為開啟主target,選擇capability,找到App Group,開啟:
- 在主Target下為這個app group新增一個名稱,然後去Extension的target下去採用相同操作,並勾選這個group
- 我們可以採用UserDefault作為主app與widget之間的共享儲存。但是此處不能使用standardUserDefaults,只能通過suiteName的方式來進行共享,且名字是之前在app group中新增的名稱,程式碼如下:
let userDefault = UserDefaults(suiteName: "group.nimoAndHisFriend.watchDemo")
複製程式碼
- 在Widget的ViewController裡寫入相應的讀取邏輯程式碼:
let userDefaults = UserDefaults(suiteName: "group.nimoAndHisFriend.watchDemo")
var leftTimeWhenQuit = userDefaults?.double(forKey: "lefttime")
複製程式碼
為了想讓widget裡的資料也進行同步更新,可以在extension的程式碼裡也加入一個timer來進行同步操作。這樣widge和主程式的widge即可同步
- 如果想了解更多關於Widget的使用,請參考文件
Project 18 - Spotlight Search
我學到了
- Spotlight Search的使用:
- 引入CoreSpotlight庫
import CoreSpotlight
- 這裡用CSSearchableItem來進行需要被索引的物件的新增。先建立一個CSSearchableItemAttributeSet,也就是Item的屬性類,對具體屬性進行新增,包括title,contentDescription,以及thumbData的設定。
- 需要單獨說明的是,CSSearchableItemAttributeSet物件的thumbData可以通過獲取UIImage物件後對其UIImageJPEGRepresentation方法來獲取或者UIImagePNGRepresentation方法來獲取(取決於image對應的檔案是什麼型別)
- 建立CSSearchableItem物件,並進一步通過indexSearchableItems方法將建立的CSSearchableItem新增到索引中:
- 引入CoreSpotlight庫
let tmpItems = [searchItem]
CSSearchableIndex.default().indexSearchableItems(tmpItems) { (error) in
}
複製程式碼
- 如果除錯過程中,發現模擬器上重新了之前的spotlight快取無法清除的情況,請更換新的模擬器,或者重置模擬器。或者乾脆切換成真機進行除錯,真機這種情況少一些:)
Project 17 - 3D Touch Quick Action
- 3D Touch的具體功能分成兩種:第一種是在SpringBoard里長按圖示進行直接功能跳轉,第二種是在APP內部對特定的檢視元素長按進行Peek & Pop
- 在做任何3D Touch相關功能的引入之前,務必確保使用者機型支援3D Touch。
self.window?.traitCollection.forceTouchCapability == .available
- 針對第一種功能,建立
UIApplicationShortcutItem
型別的Item,然後設定application的shortcutItems屬性即可。要注意,在設定icon時,只可以設定系統內建的集中icon,不支援自定義圖示 - 針對第二種功能,需要在想加入支援3D Touch的VC中註冊並新增相應事件
- 新增
UIViewControllerPreviewingDelegate
- 在此VC種註冊3D Touch支援。
self.registerForPreviewing(with: self, sourceView: self.view)
- 實現兩種delegate:
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController)
複製程式碼
- 如果想實現例子中的額外Action button,需要override對應的previewActionItems屬性,並返回你需要的UIPreviewAction的Array
Project 16 - LoginAnimation
我學到了
- 開始以為很簡單,普通UIView的Animation方法即可完成。後來發現彈跳效果並不是我使用的常規方法可以完成的
- 彈跳動畫需要使用
usingSpringWithDamping
來完成,其中的屬性要注意:-
usingSpringWithDamping:值越小動畫越誇張,借用網上圖來說明其區別:
-
initialSpringVelocity:值越大則起始速度越大,再借用網上圖片來說明其區別:
-
options的各個動畫曲線有何區別:可以看圖來進行區分:
-
- UIView.animation的usingSpringWithDamping與不帶usingSpringWithDamping的引數動畫有什麼區別呢?可以看下圖:
Project 15 - Tumblr Menu
我學到了
- 這個例子本質上是對動畫+BlurEffect
- 三排的動畫有一個先後順序,這個可以通過animation的delay引數進行調節
- button的上圖下文效果需要設定,這裡自定義了一個CustomButton,對樣式進行了封裝。參考了這篇文章
Project 14 - Video Splash
我學到了
- 建立一個AVPlayerViewController,並將其view放到背景中
- 之後結合AVPlayerViewController進行視訊播放,並自動迴圈
- 視訊播放部分借鑑了此篇文章中的第十個用例,據說也是參考了一個叫VideoSplashViewController的庫
Project 13: Animation In TableViewCell
我學到了
- 開始的思路是在willDisplay的delegate裡進行動畫操作,效果良好,但是發現在滾動cell時發生cell錯亂的現象,原因是在滾動時cell重繪導致重新呼叫willDisplay進而座標錯誤。粗看了下,解決起來有點兒麻煩,於是換思路。以此這種“進場動畫”不應該在渲染過程中的delegate中執行。
- 將動畫放到ViewWillAppear裡來做。可以通過tableView的visibleCells獲取將要顯示的所有cell的Array,逐一遍歷來進行動畫操作。
- 改變Cell的動畫,採用上一章所說的usingSpringWithDamping的動畫,usingSpringWithDamping設定為0.8,initialSpringVelocity設定為0.(不然動畫會彈跳過大,造成順次露出白色間隙,很不美觀)
- 改變Cell的具體方式,既可以直接操作
cell.frame.origin.y
,也可以通過cell.transform = CGAffineTransform(translationX: 0, y: tableHeight)
,效果是一樣的。不過如果要用到縮放或者旋轉的動畫,恐怕只能使用後者了。 - 動畫確實是很有意思的:)
Project 12 - Emoji Slot Machine
我學到了
- 乍一看沒思路,本來打算用三個collectionView來做,但是發現有點兒複雜
- 後來轉變思路,用UIPickerView來做,component設定為3即可
- 隨機數用arc4random()來算出來,之後使用UIPickerView的selectRow方法進行設定值即可達到老虎機的效果
- 為了模擬,不能讓pickerView轉到第一個或者最後一個,不然就會碰到邊界了,因此在算隨機Row時,使用
Int(arc4random())%(emojiArray.count - 2) + 1
的方法來實現 - 三個同時一致的情況實在太少了,因此為了方便模擬,我加了個雙擊操作,雙擊強制出666。。。
- 這個case還挺有意思的,哈哈
Project 11 - Gradient in TableView
我學到了
- 這個比較簡單,注意將CAGradientLayer應用在UITableViewCell上即可
- 建議將CAGradientLayer作為cell的backgroundView,而不是直接在cell.layer上進行新增
- 美觀起見,隱藏掉Cell的Select效果以及separatorStyle: table.separatorStyle = .none cell.selectionStyle = .none
Project 10 - Stretchy Header
我學到了
- 通過監聽ScrollView(及其子類)的scrollViewDidScroll代理可以知道scrollView被拉動的位移(offset)
- 通過位移以及限定的縮放值可以得出圖片需要放大的倍率
- 通過設定ImageView的transform來完成修改即可,核心程式碼為
bannerImgView.transform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
複製程式碼
Project 9 - Swipeable Cell
我學到了
簡單起見,我用Project 13的程式碼基礎上進行修改,換了個清爽的綠色:)
- 實現editActionsForRowAt這個delegate方法,返回值是Array,新建幾個你需要的功能返回即可
- 每一個Action直接通過UITableViewRowAction的init方法新建即可。在新建方法裡有block,直接將點選邏輯寫進去就行了。
- 這種互動適用於Accessory比較簡單的情況,例如對互動按鈕大小和內容無要求的情況;如果有特殊要求,需要自定義UITableViewCell,手動控制Cell與捕捉UIPanGesture來進行實現。注意,這種方式要排除上下滑動Cell的情況,不要錯誤觸發。
Project 8 - Color Gradient
我學到了
- 顏色漸變效果採用的是類CAGradientLayer
- 色彩空間的概念可以藉助於Color陣列來實現,注意,成員變數是CGColor型別,然後通過設定CAGradientLayer的colors屬性來實現
- 上下滑動時改變顏色是通過加PanGestureRecognizer來實現。具體效果參考了應用Solar
Project 7 - Simple Photo Browser
我學到了
- 縮放圖片的方式:將imageView新增到ScrollView上
- 設定好scrollView的max/minZoomScale
- 設定好delegate物件,至少實現viewForZooming的代理方法
Project 6 - Video Player
我學到了
- AVPlayer:視訊播放器實體
- AVPlayerViewController:簡單封裝了的視訊播放器,有簡單的控制功能
- AVPlayerLayer:視訊的Layer層,所有功能需要寫控制元件進行控制,適合對播放器進行深度開發
- 後臺播放的plist設定方式
- do...catch...語法的使用
- background modes的設定。
- 如何做到app在後臺長期執行:參考簡書的文章
- 如何顯示鎖屏資訊,以及如何響應鎖屏設定(實現remoteControlReceived的代理方法)
Project 5 - Pull To Refresh
我學到了
- 下拉重新整理元件: UIRefreshControl
設定好提示文字
attributedTitle
,新增好target事件(UIControlEvents.valueChanged
事件)後,新增到tableView中,即可
Project 4 - Limited Input Text Field
我學到了
- 通過新建UIBarButtonItem來建立navigationBarItem的左右Item
- 通過TextView的textViewDidChange事件捕捉當前輸入內容,從而進行限制輸入字數
- 通過監聽NSNotification.Name.UIKeyboardWillChangeFrame事件來監視Keyboard的彈出和收起。在對應回撥中,通過note.userInfo?[UIKeyboardFrameEndUserInfoKey]來拿到鍵盤的endFrame,從而拿到鍵盤的高度,對計數器進行frame操作
- 同理,通過note.userInfo?[UIKeyboardAnimationDurationUserInfoKey]拿到鍵盤的動畫duration,進而可以通過UIView的animation動畫做到同步變化計數器的frame
Project 3 - Find My Position
我學到了
- 定位點配置: 在plist中新增配置: NSLocationAlwaysUsageDescription
- 用CLLocationManager來進行定位
- 在逆地址解析的方法reverseGeocodeLocation呼叫時如果遇到了block中一直出現
Domain=GEOErrorDomain Code=-8 "(null)"
之類的錯誤,將CLGeocoder
改成全域性變數即可 - 之後這種簡單功能可以直接通過蘋果內建方法來完成,不需要再通過引入高德SDK(省去了高德SDK的大小)
Project 2: Watch Demo
我學到了
- Update cocoaPods to 1.2.0
- Learn how to use SnapKit (Quite similar with Masonry)
- Learn how to use Timer in Swift
- 我學到了: guard語句,詳見 guard詳解
Project 1: Change Custom Font
我學到了
-
如何修改字型屬性,熟悉字型屬性
-
字型名稱可以去storyboard中查詢,或者通過如下程式碼來進行查詢:
func printAllSupportedFontNames() { let familyNames = UIFont.familyNames for familyName in familyNames { print("++++++ \(familyName)") let fontNames = UIFont.fontNames(forFamilyName: familyName) for fontName in fontNames { print("----- \(fontName)") }}} 複製程式碼
寫在最後:
能堅持看到這裡的,我給你們手動雙擊666!
實話實說,文章有點標題黨,實際開發時間是40天左右,因為開發時間在下班後到睡覺前,所以有時因為要出去聚餐,有時犯懶,還有時晚上要你懂得,所以完成這三十個專案的時間比計劃的時間要長。。。
寫完這些專案,感覺上一方面是提高了使用Swift語言的熟練度,另一方面更是複習了一遍iOS開發的知識點,因為寫到後來我已經基本感覺不出來跟用OC開發有什麼思路上的差異。這也回答了別人問過我的問題,“如果我現在學iOS開發,是應該學OC還是Swift”:
我覺得從iOS SDK的熟悉角度來說,沒有本質區別,如果熟悉OC下對應語法去使用Swift寫沒有太大區別。所以與其花時間糾結不如趕緊找兩個專案上手進行練習。
下一步,我打算再重新梳理下Swift語法,對這些專案進行小規模的重構,從結構上去看看能否挖掘到Swift的特性,從另一個角度(目前是功能角度)來學習Swift。所以也許還會有下一篇。