Records of my Swift iOS development journey.
A GitBook version of the notes below has been created, check here: Swift Newbie.
If you want to contribute, please contact me ([email protected]).
部分项目截图、涉及内容的介绍:《Intro to Projects》。例如:
- FenghuangXinwen UI:
working
- Instagram UI:
working
- Weibo UI:
working
- PokeDex:
working
- MySongs:
working
- Calculator:
working
- A Simple Web Browser:
working
- My Rock Pet:
working
- MyHood:
working
- To-do List:
working
- Trapper (Tapper):
working
Articles written by myself during my journey to iOS development. The articles are listed in reverse chronological order.
- 【译】Core Graphics,第四部分:Path!Path!
- 【译】哥们儿,我的方法哪儿去了?
- 【译】Core Graphics,第三部分:线
- 【译】Core Graphics, 第二部分:说说 context (上下文)
- 【译】Core Graphics,第一部分:序章
- react-native-lahk-marquee-label(跑马灯文字组件)
- SlidingForm
- DanmuManager 一个简单的弹幕工具
- Pauseable Timer 一个可暂停的计时器
- UICollectionView 总结
- 【译】UICollectionView 轻松重排
- 模拟凤凰新闻 | 更复杂的标签动画 - Swift 实现多个 TableView 的侧滑与切换
- AutoLayout 中需要注意的点
- Swift 实现多个 TableView 的侧滑与切换(模拟 instagram 系列)
- 实现 instagram 底部弹出菜单的一个例子(模拟 instagram 系列)
- 自定义 UITabBar 总结(一个模拟 instagram TabBar 的例子)
- 仿微博 iOS 客户端 TabBar 中间按钮
- 【译】iOS 基础:Frames、Bounds 和 CGGeometry
- AutoLayout:constraint priority 约束优先级(九宫格续,一个更优方案)
- AutoLayout:UITableViewCell 自适应高度的一个例子
- UISearchBar(一)修改背景层和输入框层的背景颜色和边框颜色
videos better watch again:
- blackmoondev
- Get clear idea of MVC: Model View Controller (MVC)
- Extensions are fun: Extensions
- How does clipsToBounds work?: StackOverflow: How does clipsToBounds work?
- Parse JSON with NSJSONSerialization: How to parse JSON using NSJSONSerialization
- Error handling in Swift 2: try, catch, do and throw: Error handling in Swift 2: try, catch, do and throw
Transform
print(img.frame.height)
var tf = CATransform3DIdentity
tf.m34 = 1/(-500)
tf = CATransform3DRotate(tf, -45.0 * CGFloat(M_PI) / 180.0, 1.0, 0.0, 0.0)
self.img.layer.transform = tf
print(img.frame.height)
音频截取
- enable-disable-auto-layout-constraints
- dynamic-height-issue-for-uitableview-cells-swift/36185105#36185105
- iOS Fundamentals: Frames, Bounds, and CGGeometry
- about UILabel
- setting-cursor-position-for-a-uitextfield-in-swift
- 实现图片拼接的iOS代码
If the layout isn't what you expect, check if you've added the constraints!!!
Use 'Equal Widths' to set the width of any StackView inside ScrollView, otherwise the width will go wrong.
1 set storyboard_id
2 for example:
Swift let tv = self.storyboard?.instantiateViewControllerWithIdentifier("TapViewController") as! TapViewController self.presentViewController(tv, animated: true, completion: nil)
###3.how to load and play a audio
How to play sounds using AVAudioPlayer
drag the audio file to the app directory and just press enter
import UIKit
import AVFoundation
class ViewController: UIViewController{
var btnSound: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("btn", ofType: "wav")
let soundUrl = NSURL(fileURLWithPath: path!)
do {
try btnSound = AVAudioPlayer(contentsOfURL: soundUrl)
btnSound.prepareToPlay()
} catch {
}
}
...
@IBOutlet weak var monsterImg: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
var imgArray = [UIImage]()
//init your image array, for example
for var x = 1; x <= 4; x++ {
let img = UIImage(named: "idle\(x).png")
imgArray.append(img!)
}
monsterImg.animationImages = imgArray
monsterImg.animationDuration = 0.8
monsterImg.animationRepeatCount = 0
monsterImg.startAnimating()
//animationImages
//An array of UIImage objects to use for an animation.
//animationDuration
//The amount of time it takes to go through one cycle of the images.
//The time duration is measured in seconds. The default value of this property is equal to the number of images multiplied by 1/30th of a second. Thus, if you had 30 images, the value would be 1 second.
//animationRepeatCount
//Specifies the number of times to repeat the animation.
//The default value is 0, which specifies to repeat the animation indefinitely.
}
import Foundation
import UIKit
class DragImg: UIImageView {
var originalPosition: CGPoint!
//why override this?
override init(frame: CGRect) {
super.init(frame: frame)
}
//what's this?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
originalPosition = self.center
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let position = touch.locationInView(self.superview)
self.center = CGPointMake(position.x, position.y)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.center = originalPosition
}
}
var timer: NSTimer!
timer = NSTimer.scheduledTimerWithTimeInterval(TIME_INCREMENT, target:self, selector: "FUNCTION", userInfo: nil, repeats: BOOL)
//somewhere
NSNotificationCenter.defaultCenter().postNotification(NSNotification(name: "NOTIFICATION_NAME", object: nil))
//somewhere else
NSNotificationCenter.defaultCenter().addObserver(self, selector: "FUNCTION", name: "NOTIFICATION_NAME", object: nil)
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
An article about 'init': Swift init patterns
pass message to the next segue
//somewhere
performSegueWithIdentifier("IDENTIFIER", sender: A_SENDER)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyOBject?) {
if segue.identifier == "IDENTIFIER" {
if let whatVC = segue.destinationViewController as ? SomeViewController {
if let theString = sender as? String {
whatVC.someStr = theString
}
}
}
}
extension Double {
var currency: String {
return "$\(self)"
}
}
edit the info.plist
NSAppTransportSecurity Dictionary
NSAllowsArbitraryLoads Boolean YES
Add the following to your viewDidLoad():
let tap = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
and then add the following method declaration:
func dismissKeyboard()
{
view.endEditing(true)
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
参考:Automatically resizing UITableViewCells with Dynamic Type and NSAttributedString
view.frame.CGRectMake(0 , 0, self.view.frame.width, self.view.frame.height * 0.7)
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
collectionView.collectionViewLayout = layout
override willDisplayCell
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
//Animation Code
}
drawImage(UIImage(named: "image")!, size: CGSizeMake(30, 30), renderColor: UIColor(red: 149.0/255, green: 149.0/255, blue: 149.0/255, alpha: 1))
let text = (stringName as NSString).stringByReplacingOccurrencesOfString("whatever", withString: "")
clean and re-build
if Int(strToCheck) == nil {
print("Not Int")
} else {
print("Is Int")
}
String.localizedStringWithFormat(NSLocalizedString("Blahblahblah %d.", comment: "Some Comment"), intVar)
import UIKit
class ExampleAlertView: UIView {
@IBOutlet weak var wechatPayButton: UIButton!
@IBOutlet weak var alipayButton: UIButton!
@IBOutlet weak var amountTextField: UITextField!
@IBOutlet weak var infoLabel: UILabel!
private var shadowBtn: UIButton!
let ApplicationDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
private var showView: UIView {
return ApplicationDelegate.window!
}
private var confirmClosure: (() -> ())?
private var cancelClosure: (() -> ())?
@objc private func shadowBtnClick() {
UIView.animateWithDuration(0.15, animations: {
self.shadowBtn.alpha = 0
self.alpha = 0
}) { (success) in
self.shadowBtn.removeFromSuperview()
self.removeFromSuperview()
}
}
class func exampleAlertView(initialMoney: String, canInput: Bool, confirmClosure:(()->()), cancelClosure: (() -> ())) -> DXPayAlertView {
let alert = NSBundle.mainBundle().loadNibNamed("ExampleAlertView", owner: nil, options: nil).first as! ExampleAlertView
alert.confirmClosure = confirmClosure
alert.cancelClosure = cancelClosure
return alert
}
override func awakeFromNib() {
self.layer.cornerRadius = 10
self.layer.masksToBounds = true
self.shadowBtn = UIButton(frame: UIScreen.mainScreen().bounds)
self.shadowBtn.addTarget(self, action: #selector(ExampleAlertView.shadowBtnClick), forControlEvents: .TouchUpInside)
self.shadowBtn.backgroundColor = UIColor.blackColor()
shadowBtn.alpha = 0.4
UIApplication.sharedApplication().keyWindow?.addSubview(shadowBtn)
}
func show() {
let size = UIScreen.mainScreen().bounds.size
self.shadowBtn.center = CGPoint(x: size.width/2, y: size.height/2)
self.center = self.shadowBtn.center
self.shadowBtn.frame = self.showView.bounds
self.shadowBtn.alpha = 0
self.alpha = 0
self.transform = CGAffineTransformMakeScale(1.2, 1.2)
UIView.animateWithDuration(0.25) {
self.shadowBtn.alpha = 0.4
self.showView.addSubview(self.shadowBtn)
UIView.animateWithDuration(0.25, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.1, options: .CurveEaseInOut, animations: {
self.showView.addSubview(self)
self.transform = CGAffineTransformMakeScale(1.0, 1.0)
self.alpha = 1
}, completion: { (_) in
self.transform = CGAffineTransformMakeScale(1.0, 1.0)
self.showView.addSubview(self)
self.alpha = 1
})
}
}
@IBAction func confirmBtnClick(sender: UIButton) {
self.confirmClosure?()
shadowBtnClick()
}
@IBAction func cancelBtnClick(sender: UIButton) {
self.cancelClosure?()
shadowBtnClick()
}
}
deinit{
print("WhicheverController deinit")
// some code
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let url = NSURL(string: image.url)
let data = NSData(contentsOfURL: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check
imageView.image = UIImage(data: data!)
//If you want to make the code run async, you can easily achieve this with GCD:
let url = NSURL(string: image.url)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let data = NSData(contentsOfURL: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check
dispatch_async(dispatch_get_main_queue(), {
imageView.image = UIImage(data: data!)
});
}
26.double tap gesture will cause tab switch slowing down, the reason is it needs a minimum amount of time to wait for possible taps.
func getStatusTime(dateStr: String) -> String {
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = dateFormatter.dateFromString(dateStr)!
//判断是否是同一年
if isSameYear(date) {
let calendar = NSCalendar.currentCalendar()
if calendar.isDateInToday(date) {
let time = abs(Int32(date.timeIntervalSinceNow))
if time < 60 {
return "刚刚"
}else if time < 60 * 60 {
return "\(time/60) 分钟前"
}else{
return "\(time / 60 / 60)小时前"
}
}else if calendar.isDateInYesterday(date) {
dateFormatter.dateFormat = "HH:mm"
return "昨天" + dateFormatter.stringFromDate(date)
}else {
dateFormatter.dateFormat = "MM-dd HH:mm"
return dateFormatter.stringFromDate(date)
}
}else {
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
return dateFormatter.stringFromDate(date)
}
}
func isSameYear(date: NSDate) -> Bool{
dateFormatter.dateFormat = "yyyy"
return dateFormatter.stringFromDate(date) == dateFormatter.stringFromDate(NSDate())
}
You can do that by setting the properties of the textContainer like so:
textView.textContainer.maximumNumberOfLines = 2;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
To make imageWithRenderingMode work, you need to seperate creating UIImage and setting renderingmode
// Correct way
let image = UIImage(named: "ImageName")!
self.someImgview.image = image.imageWithRenderingMode(.AlwaysTemplate)
// Wrong way
self.someImgview.image = UIImage(named: "ImageName")!.imageWithRenderingMode(.AlwaysTemplate)
To update UITextView height with it's content height, you need to update the correspond height constraint. Otherwise, it won't work.
extension UITextView {
func heightThatFitsContent() -> CGFloat {
let fixedWidth = self.frame.width
let newSize = self.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)))
return newSize.height
}
}
**textViewHeightConstraint.constant = textview.heightThatFitsContent()**
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
func keyboardWillShow(notification:NSNotification) {
let userInfo:NSDictionary = notification.userInfo!
let keyboardFrame:NSValue = userInfo.valueForKey(UIKeyboardFrameEndUserInfoKey) as! NSValue
let keyboardRectangle = keyboardFrame.CGRectValue()
let keyboardHeight = keyboardRectangle.height
}
func getVersion() -> String {
let dict = NSBundle.mainBundle().infoDictionary
return dict!["CFBundleShortVersionString"]! as! String
}
func getBuildVersion() -> String {
let dict = NSBundle.mainBundle().infoDictionary
return dict!["CFBundleVersion"]! as! String
}
func showCustomAlert(title: String, message: String, textfiledSetting: (textfield: UITextField)->()), cancelAct: ((alert: UIAlertController)->())?, confirmAct: ((alert: UIAlertController)->())?) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let cancel = UIAlertAction(title: "Cancel", style: .Cancel) { (_) -> Void in
cancelAct?(alert: alert)
}
let confirm = UIAlertAction(title: "Confirm", style: .Default) { (_) -> Void in
confirmAct?(alert: alert)
}
alert.addTextFieldWithConfigurationHandler(textfieldSetting)
alert.addAction(confirm)
alert.addAction(cancel)
ApplicationDelegate.window?.rootViewController?.presentViewController(alert, animated: true, complettion: nil)
}
UIFont(name: "Roboto-Regular", size: 15) ×
UIFont.init(name: "Roboto-Regular", size: 15) √
36.hex string to NSData Reference
extension String {
/// Create `NSData` from hexadecimal string representation
///
/// This takes a hexadecimal representation and creates a `NSData` object. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
///
/// - returns: Data represented by this hexadecimal string.
func dataFromHexadecimalString() -> NSData? {
let data = NSMutableData(capacity: characters.count / 2)
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .CaseInsensitive)
regex.enumerateMatchesInString(self, options: [], range: NSMakeRange(0, characters.count)) { match, flags, stop in
let byteString = (self as NSString).substringWithRange(match!.range)
var num = UInt8(byteString, radix: 16)
data?.appendBytes(&num, length: 1)
}
return data
}
}
// Hex Color Convenience Function
extension UIColor {
convenience init(red: Int, green: Int, blue: Int) {
assert(red >= 0 && red <= 255, "Invalid red component")
assert(green >= 0 && green <= 255, "Invalid green component")
assert(blue >= 0 && blue <= 255, "Invalid blue component")
self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
}
convenience init(netHex:Int) {
self.init(red:(netHex >> 16) & 0xff, green:(netHex >> 8) & 0xff, blue:netHex & 0xff)
}
}
You can override pickerview:attributtedStringForTitle to change title color. However, you cannot change font with this method.
To customize font, use pickerview:viewForRow
41.if you see the error "Enum case 'someCase' not found in type 'someEnumType'", add ! to 'someEnumTypeVar'
// Error
switch someEnumTypeVar {
case .someCase:
<#code#>
default:
<#code#>
}
// Fixed
switch someEnumTypeVar! {
case .someCase:
<#code#>
default:
<#code#>
}
- Trapper should actually be Tapper. It's a typing mistake.
- You might have noticed that there are 2 No. 11 in the Notes part. Sorry, too lazy to change the order of the rest 30+ sequence numbers XD.