Swift小demo学习笔记
2017-07-26 本文已影响76人
黑化肥发灰
之前看了一些swift相关的介绍,但是一直没怎么写代码,但是不写代码肯定掌握不好一门语言的,纸上得来终觉浅,绝知此事要躬码。正好在github上找到了一个好东西,有三四十个小demo,我决定参考他们自己重写一遍了。本篇笔记记录下每个demo里面自己不熟的地方,后面可以随时复习。
参考链接如下:SwiftPractice
1.GHWSimpleClock
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight))
let vc = GHWViewController()
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
return true
}
[self.buttonLeft, self.buttonRight].forEach {
($0.addTarget(self, action: #selector(buttonTap(sender:)), for: .touchUpInside))
}
func buttonTap(sender : UIButton) {
switch sender {
case self.buttonLeft:
sender.isSelected = !sender.isSelected
sender.isSelected ? start() : stop()
break
case self.buttonRight:
sender.isSelected = !sender.isSelected
sender.isSelected ? pause() : continue1()
break
default:
return
}
}
2. GHWCustomFont
return tableViewHeight/CGFloat(dataArray.count)
//设置状态栏样式
/*设置状态栏:
@available(iOS 7.0, *)
open var preferredStatusBarStyle: UIStatusBarStyle { get }//样式
@available(iOS 7.0, *)
open var prefersStatusBarHidden: Bool { get }//是否隐藏
*/
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
/*代码创建,并且没有注册cell的情况下,用dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?
如果已经注册了,或者用的xib,就使用dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell
*/
//??空合运算符,a ?? b,对可选类型a进行判断,为nil默认值为b,不为空就解封
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: reuseIdentifier)
let text = datas[indexPath.row]
cell.textLabel?.text = text
cell.textLabel?.textColor = .white
cell.textLabel?.font = UIFont(name: fontNames[fontNumber], size: 24)
cell.backgroundColor = .black
return cell
}
3. PlayLocalVideo
struct VideoModel {
let image: String
let title: String
let source: String
}
// 不加问号的话会报错:class has no initializers, 这里凡是没有初始化的都要加上问号
var player : AVPlayer?
var playerViewController : AVPlayerViewController?
let dataArray = [
VideoModel(image: "videoScreenshot01", title: "Introduce 3DS Mario", source: "Youtube - 06:32"),
VideoModel(image: "videoScreenshot02", title: "Emoji Among Us", source: "Vimeo - 3:34"),
VideoModel(image: "videoScreenshot03", title: "Seals Documentary", source: "Vine - 00:06"),
VideoModel(image: "videoScreenshot04", title: "Adventure Time", source: "Youtube - 02:39"),
VideoModel(image: "videoScreenshot05", title: "Facebook HQ", source: "Facebook - 10:20"),
VideoModel(image: "videoScreenshot06", title: "Lijiang Lugu Lake", source: "Allen - 20:30")
]
4. WelcomeView
//遍历数组,同时获得index
for (index,value) in images.enumerated() {
let imageView = UIImageView(frame: CGRect(x: YHWidth*CGFloat(index), y: 0, width: YHWidth, height: YHHeight))
imageView.image = UIImage(named: value)
//限制边界
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
scrollBG.addSubview(imageView)
}
5. PictureBrowse
// 毛玻璃
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
let myLayout = UICollectionViewFlowLayout()
myLayout.scrollDirection = .horizontal
myLayout.itemSize = CGSize(width: kCellWidth, height: kCellHeight)
myLayout.minimumLineSpacing = 20
myLayout.minimumInteritemSpacing = 20
myLayout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
collectionView = UICollectionView(frame: CGRect(x : 0, y : (kScreenHeight - kCellHeight)/2, width : kScreenWidth, height : kCellHeight), collectionViewLayout: myLayout)
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.backgroundColor = .clear
collectionView.register(GHWCollectionViewCell.self, forCellWithReuseIdentifier: identifier)
var data: CollectionModel? {
/*属性观察器
willSet 在新的值被设置之前调用
didSet 在新的值被设置之后立即调用
*/
didSet {
updateUI()
}
}
private func updateUI() {
featureImageView.image = data?.featuredImage
interestTitleLabel.text = data?.title
interestDetailLabel.text = data?.descriptions
}
static func createInterests() -> [CollectionModel] {
return [
CollectionModel(title: "Training like this, #bodyline", descriptions: "Create beautiful apps. We walked to Antartica yesterday, and camped with some cute pinguines, and talked about this wonderful app idea. 🐧⛺️✨", featuredImage: UIImage(named: "bodyline")!)
]
}
var title: String?
var featureImage: UIImage?
var descriptions: String?
// 这边不需要override,只有真正敲代码才能知道这些问题了,所以删掉
init(title: String, descriptions: String, featureImage: UIImage) {
self.featureImage = featureImage
self.title = title
self.descriptions = descriptions
}
6. Current Location
下面这样是好的:
7. SystemRefreshControl
let newData = ["1111", "2222", "3333", "4444"]
var dataArray = ["11", "22", "33", "44"]
let identifier = "identifier"
func addData() {
dataArray.append(contentsOf: newData)
tableView.reloadData()
refreshControl.endRefreshing()
}
8 GradientColor
import UIKit
let YHRect = UIScreen.main.bounds
let YHHeight = YHRect.size.height
let YHWidth = YHRect.size.width
class ViewController: UIViewController {
let gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
func setupView() {
setupGradientLayer()
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(randomColor), userInfo: nil, repeats: true)
}
func setupGradientLayer() {
gradientLayer.frame = YHRect
let color1 = UIColor(white: 0.5, alpha: 0.2).cgColor
let color2 = UIColor(red: 1.0, green: 0, blue: 0, alpha: 0.4).cgColor
let color3 = UIColor(red: 0, green: 1, blue: 0, alpha: 0.3).cgColor
let color4 = UIColor(red: 0, green: 0, blue: 1, alpha: 0.3).cgColor
let color5 = UIColor(white: 0.4, alpha: 0.2).cgColor
gradientLayer.colors = [color1, color2, color3, color4, color5]
gradientLayer.locations = [0.10, 0.30, 0.50, 0.70, 0.90]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
view.layer.addSublayer(gradientLayer)
}
func randomColor() {
view.backgroundColor = UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
9. GradientColor
import UIKit
class GHWMainViewController: UIViewController, UIScrollViewDelegate {
let bottomView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
let scrollView = UIScrollView(frame: kScreenRect)
let imageView = UIImageView(image: UIImage(named: "steve"))
override func viewDidLoad() {
super.viewDidLoad()
configUI()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
func configUI() {
view.layer.contents = UIImage(named: "steve")?.cgImage
bottomView.frame = kScreenRect
scrollView.backgroundColor = .clear
scrollView.delegate = self
scrollView.contentSize = imageView.bounds.size
scrollView.addSubview(imageView)
view.addSubview(bottomView)
view.addSubview(scrollView)
setupZoomScale()
scrollView.zoomScale = scrollView.minimumZoomScale
reCenterImage()
}
func setupZoomScale() {
let scrollViewSize = scrollView.bounds.size
let imageSize = imageView.bounds.size
let widthScale = scrollViewSize.width / imageSize.width
let heightScale = scrollViewSize.height / imageSize.height
let miniScale = min(widthScale, heightScale)
scrollView.minimumZoomScale = miniScale
scrollView.maximumZoomScale = 3.5
}
func reCenterImage() {
let scrollViewSize = scrollView.bounds.size
let imageSize = imageView.bounds.size
let horizontalSpace = imageSize.width < scrollViewSize.width ? (scrollViewSize.width - imageSize.width)/2.0 : 0
let verticalSpace = imageSize.height < scrollViewSize.height ? (scrollViewSize.height - imageSize.height)/2.0 : 0
scrollView.contentInset = UIEdgeInsets(top: verticalSpace, left: horizontalSpace, bottom: verticalSpace, right: horizontalSpace)
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
reCenterImage()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
10. VideoBackground
import Foundation
import UIKit
extension UIButton {
func buttonTitle(configTitle title: String) {
setTitle(title, for: .normal)
titleLabel?.textColor = .white
layer.cornerRadius = 5
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 1
}
}
func configMediaPlayer() {
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "moments", ofType: "mp4")!)
mediaPlayerVC.player = AVPlayer(url: url)
mediaPlayerVC.videoGravity = AVLayerVideoGravityResizeAspectFill
mediaPlayerVC.showsPlaybackControls = false
mediaPlayerVC.view.alpha = 1
mediaPlayerVC.view.frame = kScreenRect
NotificationCenter.default.addObserver(self, selector: #selector(repeatPlay), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: mediaPlayerVC.player?.currentItem)
view.addSubview(mediaPlayerVC.view)
view.sendSubview(toBack: mediaPlayerVC.view)
mediaPlayerVC.player?.play()
}
func repeatPlay() {
mediaPlayerVC.player?.seek(to: kCMTimeZero)
mediaPlayerVC.player?.play()
}
11. colorProgress
import UIKit
class GHWMainViewController: UIViewController {
let progresss = GHWProgress(frame:CGRect(x: 20, y: 200, width: kScreenWidth - 40, height: 20))
override func viewDidLoad() {
super.viewDidLoad()
configUI()
}
func configUI() {
view.addSubview(progresss)
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
self.progresss.progress = self.progresss.progress + 0.03
if self.progresss.progress >= 1.0 {
timer.invalidate()
}
}
}
}
import UIKit
class GHWProgress: UIView, CAAnimationDelegate {
let gradientLayer = CAGradientLayer()
let maskLayer = CALayer()
var progress: CGFloat = 0.0 {
didSet {
reConfigUI()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
configUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func reConfigUI() {
maskLayer.frame = CGRect(x: 0.0, y: 0.0, width: progress * bounds.size.width, height: bounds.size.height)
}
private func configUI() {
let whiteBG = CALayer()
whiteBG.frame = bounds
whiteBG.cornerRadius = bounds.size.height / 2.0
whiteBG.borderColor = UIColor.white.cgColor
whiteBG.borderWidth = 1.0
layer.addSublayer(whiteBG)
gradientLayer.frame = bounds
gradientLayer.cornerRadius = bounds.size.height / 2.0
gradientLayer.borderColor = UIColor.white.cgColor
gradientLayer.borderWidth = 1.0
var colors = [CGColor]()
for hue in stride(from: 0, through: 360, by: 5) {
let color = UIColor(hue: CGFloat(hue)/360.0, saturation: 1.0, brightness: 1.0, alpha: 1.0).cgColor
colors.append(color)
}
gradientLayer.colors = colors
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
layer.addSublayer(gradientLayer)
reConfigUI()
maskLayer.cornerRadius = bounds.size.height/2.0
maskLayer.backgroundColor = UIColor.white.cgColor
gradientLayer.mask = maskLayer
}
private func performAnimation() {
var colors = gradientLayer.colors
let color = colors?.popLast() as! CGColor
colors?.insert(color, at: 0)
let animation = CABasicAnimation(keyPath: "colors")
animation.fromValue = gradientLayer.colors
animation.toValue = colors
animation.duration = 0.2
animation.fillMode = kCAFillModeForwards
animation.delegate = self
gradientLayer.add(animation, forKey: "gradient")
gradientLayer.colors = colors
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
performAnimation()
}
}
12. TableHeadView
import UIKit
let YHRect = UIScreen.main.bounds
let YHHeight = YHRect.size.height
let YHWidth = YHRect.size.width
let HeadViewHeight = YHHeight/3.0
class ViewController: UIViewController {
let datas = ["下","拉","可","以","出","现","很","神","奇","的","事","情","yo","yo","yo","yo","yo","yo"]
let tableView = UITableView(frame: YHRect, style: .plain)
let resueIdentifer = "CustomCell"
let headView = UIImageView(frame: CGRect(x: 0.0, y: 0.0, width: YHWidth, height: HeadViewHeight))
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setupView() {
self.automaticallyAdjustsScrollViewInsets = false
headView.backgroundColor = .white
headView.contentMode = .scaleAspectFill
headView.clipsToBounds = true
view.addSubview(headView)
//加载图片,注意这里是URL,不是NSURL
let url = URL(string: "http://c.hiphotos.baidu.com/zhidao/pic/item/5ab5c9ea15ce36d3c704f35538f33a87e950b156.jpg")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let _ = data,error == nil else { return }
//回到主线程
DispatchQueue.main.sync {
self.headView.image = UIImage(data: data!)
}
}
task.resume()
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: resueIdentifer)
tableView.showsVerticalScrollIndicator = false
//下面两句必不可少,否则会出现第一次加载时位置不对的情况,这个地方一定要注意哦,非常关键
tableView.contentInset.top = HeadViewHeight
tableView.contentOffset = CGPoint(x: 0.0, y: -HeadViewHeight)
// tableView.scrollIndicatorInsets.top = HeadViewHeight//右边指示器的位置
view.addSubview(tableView)
view.sendSubview(toBack: tableView)
}
}
extension ViewController:UITableViewDataSource, UITableViewDelegate {
//MARK: - DataSource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: resueIdentifer, for: indexPath)
cell.textLabel?.text = datas[indexPath.row]
cell.textLabel?.textAlignment = .center
return cell
}
//MARK: - Delegate
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsety = scrollView.contentOffset.y + scrollView.contentInset.top
if offsety <= 0 {
headView.frame = CGRect(x: 0.0, y: 0.0, width: YHWidth, height: HeadViewHeight-offsety)
}else {
let height = (HeadViewHeight-offsety) <= 0.0 ? 0.0 : (HeadViewHeight-offsety)
headView.frame = CGRect(x: 0.0, y: 0.0, width: YHWidth, height: height)
headView.alpha = height/HeadViewHeight
}
}
}
上面有涉及到tableView的,contentOffset,contentInset的使用,这里又有新的体会了。其实content包括各种header和footer,再加上cell。contentOffset就是content到frame边的距离。把握好这个就可以理解为什么开始的时候要
tableView.contentInset.top = HeadViewHeight
tableView.contentOffset = CGPoint(x: 0.0, y: -HeadViewHeight)
然后tableView.addSubView的时候,是add到content上面哦。
其实主要还是要知道哪些是content。
13. ChildVCTransition
import UIKit
let YHRect = UIScreen.main.bounds
let YHHeight = YHRect.size.height
let YHWidth = YHRect.size.width
let JumpNotification = "JUMP"
class RootVC: UIViewController {
var currentChildNumber = 0
//最简单的转场实现方式,该方式是在rootVC的子VC之间转场
override func viewDidLoad() {
super.viewDidLoad()
setupChild()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func setupChild() {
addChildViewController(ChildAVC())
addChildViewController(ChildBVC())
view.addSubview((childViewControllers.first?.view)!)
//监听跳转通知,NotificationCenter通知中心
NotificationCenter.default.addObserver(self, selector: #selector(jump), name: NSNotification.Name(rawValue: JumpNotification), object: nil)
}
func jump() {
//options:跳转的方式
transition(from: currentChildNumber==0 ? childViewControllers.first! : childViewControllers.last!,
to: currentChildNumber==0 ? childViewControllers.last! : childViewControllers.first!,
duration: 1,
options: currentChildNumber==0 ? .transitionFlipFromLeft : .transitionFlipFromRight,
animations: nil,
completion: nil)
currentChildNumber = currentChildNumber == 0 ? 1 : 0
}
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: JumpNotification), object: nil)
}
}
import UIKit
class ChildAVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
//添加一个点击手势
let tap = UITapGestureRecognizer(target: self, action: #selector(jump))
view.addGestureRecognizer(tap)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setupView() {
}
func jump() {
//发送跳转通知
NotificationCenter.default.post(name: NSNotification.Name(rawValue: JumpNotification), object: nil)
}
}
14. MusicPlayer
import UIKit
import AVFoundation
let YHRect = UIScreen.main.bounds
let YHHeight = YHRect.size.height
let YHWidth = YHRect.size.width
class ViewController: UIViewController {
let fileManager = FileManager.default
let musicPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first?.appending("/music")
var musicList = [String]()
let tableView = UITableView(frame: YHRect)
let resueIndetifier = "MusicCell"
var musicPlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
saveMusicFile()
getMusicList()
setupView()
}
override var prefersStatusBarHidden: Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*沙盒文件目录:
AppName.app:应用程序本身数据,只读。 Bundle.main.path(forResource: <#T##String?#>, ofType: <#T##String?#>)
Documents:会被itunes备份,应用程序产生的数据,不可再生的数据放在这里
Inbox:该目录可被外部应用程序访问,只读,需要自己创建
Library:
Caches:应用程序启动过程需要的信息,不会被备份,存放可再生的数据
Preferences:偏好设置,会被itunes备份
tmp:临时数据存放,会自动被删除
*/
//保存音乐文件--这一步不是必须的,只是为了熟悉swift存储文件的操作
func saveMusicFile() {
let names = ["成都","童话镇"]
//在caches创建一个music文件夹
/*try try! try?区别:
try:两种情况,①do {let result = try func()} catch {} 处理throw ②let result = try func() 忽略throw
try!:相当于可选类型!,如果不throw就不会出任何问题,一旦throw,程序就会crash
try?:把throw转换成是否是nil,而不处理具体的结果。如①if let result = try? func() {} else {} ②guard let result = try? func() else {}
*/
print(musicPath)
do {
try fileManager.createDirectory(atPath: musicPath!, withIntermediateDirectories: true, attributes: nil)
print("创建成功")
} catch let err as NSError {
print("创建失败:\(err.localizedFailureReason)")
return
}
//把歌曲保存到caches里面
for name in names {
let bundlePath = Bundle.main.path(forResource: name, ofType: "m4r")
let toPath = musicPath!.appending("/\(name).m4r")
if fileManager.fileExists(atPath: toPath) {
print("已经存在\(name)")
continue
}
do {
try fileManager.copyItem(atPath: bundlePath!, toPath: toPath)
print("移动成功")
} catch let err as NSError {
print("移动失败:\(err.localizedFailureReason)")
}
}
}
//读取音乐列表
func getMusicList() {
//读取、查看文件用NSFileManager
do {
musicList = try fileManager.contentsOfDirectory(atPath: musicPath!)
print("查询成功:\(musicList)")
} catch let err as NSError {
print("查询失败:\(err.localizedFailureReason)")
}
}
func setupView() {
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
view.addSubview(tableView)
}
func playMusic(path: String) {
if musicPlayer != nil {
musicPlayer?.stop()
musicPlayer = nil
}
do {
try musicPlayer = AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
//可以通过musicPlayer的属性获得歌曲的信息,包括长度,通道等信息
print("歌曲长度:\((musicPlayer?.duration)!)")
musicPlayer?.numberOfLoops = -1//循环次数
musicPlayer?.delegate = self
musicPlayer?.play()
} catch let err as NSError {
print("播放失败:\(err.localizedFailureReason)")
}
}
}
extension ViewController:UITableViewDataSource, UITableViewDelegate ,AVAudioPlayerDelegate {
//MARK: - DataSource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return musicList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: resueIndetifier) ?? UITableViewCell(style: .default, reuseIdentifier: resueIndetifier)
cell.textLabel?.text = musicList[indexPath.row]
cell.textLabel?.textAlignment = .center
cell.textLabel?.textColor = .orange
cell.textLabel?.font = UIFont.systemFont(ofSize: 30, weight: 10)
return cell
}
//MARK: - Delegate
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
playMusic(path: musicPath!.appending("/\(musicList[indexPath.row])"))
}
//MARK: - AVAudioPlayerDelegate
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
musicPlayer = nil
}
}
15. ViewController转场动画
首先一个小问题,button上面图片下面标题,extension,这个很多地方都会有需要
import Foundation
import UIKit
extension UIButton {
//设置按钮图片在上,文字在下的效果
func alignContentVerticallyByCenter() {
contentHorizontalAlignment = .center
contentVerticalAlignment = .center
//图片与title之间有一个默认的间隔10
let offset: CGFloat = 10
//在有的iOS版本上,会出现获得不到frame的情况,加上下面这两句可以100%得到
// titleLabel?.backgroundColor = backgroundColor
// imageView?.backgroundColor = backgroundColor
//title
let titleWidth = titleLabel?.frame.size.width
let titleHeight = titleLabel?.frame.size.height
let titleLef = titleLabel?.frame.origin.x
let titleRig = frame.size.width-titleLef!-titleWidth!
//image
let imageWidth = imageView?.frame.size.width
let imageHeight = imageView?.frame.size.height
let imageLef = imageView?.frame.origin.x
let imageRig = frame.size.width-imageLef!-imageWidth!
imageEdgeInsets = UIEdgeInsets(top: 0, left: -imageLef!, bottom: titleHeight!, right: -imageRig)
titleEdgeInsets = UIEdgeInsets(top: imageHeight!+offset, left: -titleLef!, bottom: 0, right: -titleRig)
}
}
然后就是UIViewControllerAnimatedTransitioning和UIViewControllerTransitioningDelegate
UIViewControllerAnimatedTransitioning:这个接口负责切换的具体内容,也即“切换中应该发生什么”。开发者在做自定义切换效果时大部分代码会是用来实现这个接口。它只有两个方法需要我们实现。
UIViewControllerTransitioningDelegate:这个接口的作用比较简单单一,在需要VC切换的时候系统会像实现了这个接口的对象询问是否需要使用自定义的切换效果。这个接口共有四个类似的方法。
具体实现代码看下面:
import UIKit
class MenuTransitionManger: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
private var presenting = false
func offStage(amount: CGFloat) -> CGAffineTransform {
//返回平移x,y的结构体
return CGAffineTransform(translationX: amount, y: 0)
}
//关
func offStageMenuController(menuVC: MenuViewController) {
menuVC.view.alpha = 0
let topRowOffset: CGFloat = 300
let middleRowOffset: CGFloat = 150
let bottomRowOffset: CGFloat = 50
menuVC.btns[0].transform = offStage(amount: -topRowOffset)
menuVC.btns[1].transform = offStage(amount: topRowOffset)
menuVC.btns[2].transform = offStage(amount: -middleRowOffset)
menuVC.btns[3].transform = offStage(amount: middleRowOffset)
menuVC.btns[4].transform = offStage(amount: -bottomRowOffset)
menuVC.btns[5].transform = offStage(amount: bottomRowOffset)
}
//开
func onStageMenuController(menuVC: MenuViewController) {
menuVC.view.alpha = 1
menuVC.btns[0].transform = CGAffineTransform.identity
menuVC.btns[1].transform = CGAffineTransform.identity
menuVC.btns[2].transform = CGAffineTransform.identity
menuVC.btns[3].transform = CGAffineTransform.identity
menuVC.btns[4].transform = CGAffineTransform.identity
menuVC.btns[5].transform = CGAffineTransform.identity
}
//MARK: - UIViewControllerAnimatedTransitioning
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let screens: (from: UIViewController, to: UIViewController) = (transitionContext.viewController(forKey: .from)!, transitionContext.viewController(forKey: .to)!)
let menuVC = !presenting ? screens.from as! MenuViewController : screens.to as! MenuViewController
let bottomVC = !presenting ? screens.to : screens.from
let menuV = menuVC.view
let bottomV = bottomVC.view
if self.presenting {
offStageMenuController(menuVC: menuVC)
}
container.addSubview(bottomV!)
container.addSubview(menuV!)
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration,
delay: 0.0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.8,
options: [],
animations: {
self.presenting ? self.onStageMenuController(menuVC: menuVC) : self.offStageMenuController(menuVC: menuVC)
},
completion: { finished in
transitionContext.completeTransition(true)
if self.presenting {
UIApplication.shared.keyWindow?.addSubview(screens.from.view)
}
UIApplication.shared.keyWindow?.addSubview(screens.to.view)
}
)
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
//MARK: - UIViewControllerTransitioningDelegate
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = true
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = false
return self
}
}
16. 多级列表
这个demo里面重点关注
- 数据结构struct City Province
- initDatas()中对数据的处理
- viewForHeaderInSection中对手势的添加
- 采用runtime为手势增加一个tag属性,标记手势。
import UIKit
struct City {
let name: String
let alias: String
}
struct Province {
let name: String
var citys: [City]
var isOpen: Bool
}
class ViewController: UIViewController {
let tableView = UITableView(frame: YHRect, style: .plain)
let reuseIdentifer = "Cell"
let reuseHeaderIdentifer = "HeaderCell"
var datas = [Province]()
override func viewDidLoad() {
super.viewDidLoad()
initDatas()
setupView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initDatas() {
let dic = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "Province", ofType: "plist")!)
dic?.enumerateKeysAndObjects({ (key, value, roolback) in
let values = value as! NSDictionary
var citys = [City]()
for k in values.allKeys {
let city = City(name: k as! String, alias: values[k] as! String)
citys.append(city)
}
let pro = Province(name: key as! String, citys: citys, isOpen: false)
datas.append(pro)
})
}
func setupView() {
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
tableView.register(CustomHeaderView.self, forHeaderFooterViewReuseIdentifier: reuseHeaderIdentifer)
view.addSubview(tableView)
}
}
extension ViewController:UITableViewDataSource, UITableViewDelegate {
//MARK: - DataSource
func numberOfSections(in tableView: UITableView) -> Int {
return datas.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let province = datas[section]
return province.isOpen ? province.citys.count : 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer) ?? UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifer)
let city = datas[indexPath.section].citys[indexPath.row]
cell.textLabel?.text = city.name
cell.textLabel?.textColor = .orange
cell.textLabel?.textAlignment = .center
cell.detailTextLabel?.text = city.alias
cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12)
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseHeaderIdentifer) as! CustomHeaderView
func addTap() {
let tap = UITapGestureRecognizer(target: self, action: #selector(headerTap(sender:)))
tap.tag = section
view.addGestureRecognizer(tap)
}
print("手势数目:\(view.gestureRecognizers?.count)")
if let grs = view.gestureRecognizers {
grs.count > 0 ? (grs.first as! UITapGestureRecognizer).tag = section : addTap()
}else {
addTap()
}
view.buildUI(province: datas[section])
return view
}
//MARK: - GR
func headerTap(sender: UITapGestureRecognizer) {
datas[sender.tag].isOpen = !datas[sender.tag].isOpen
tableView.reloadSections([sender.tag], with: .fade)
}
//MARK: - Delegate
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
var AssociatedObjectHandle: UInt8 = 0
//采用runtime为手势增加一个tag属性,标记手势。
extension UITapGestureRecognizer {
// struct AddProperty {
// static var tag: Int = 0
// }
//
var tag: Int {
get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! Int
}
set {
objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}