版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2020.11.29 星期日 |
前言
iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面,用戶交互也是通過(guò)UIKit進(jìn)行的。感興趣的參考上面幾篇文章。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類似效果的實(shí)現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場(chǎng)和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場(chǎng)和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定義布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定義布局 (二)
28. UIKit框架(二十八) —— 一個(gè)UISplitViewController的簡(jiǎn)單實(shí)用示例 (一)
29. UIKit框架(二十九) —— 一個(gè)UISplitViewController的簡(jiǎn)單實(shí)用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡(jiǎn)單示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡(jiǎn)單示例(二)
32. UIKit框架(三十二) —— 替換Peek and Pop交互的基于iOS13的Context Menus(一)
33. UIKit框架(三十三) —— 替換Peek and Pop交互的基于iOS13的Context Menus(二)
34. UIKit框架(三十四) —— Accessibility的使用(一)
35. UIKit框架(三十五) —— Accessibility的使用(二)
36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
38. UIKit框架(三十八) —— 基于CollectionView轉(zhuǎn)盤效果的實(shí)現(xiàn)(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用協(xié)議構(gòu)建自定義Collection(一)
42. UIKit框架(四十二) —— 使用協(xié)議構(gòu)建自定義Collection(二)
43. UIKit框架(四十三) —— CALayer的簡(jiǎn)單實(shí)用示例(一)
44. UIKit框架(四十四) —— CALayer的簡(jiǎn)單實(shí)用示例(二)
45. UIKit框架(四十五) —— 支持DarkMode的簡(jiǎn)單示例(一)
46. UIKit框架(四十六) —— 支持DarkMode的簡(jiǎn)單示例(二)
47. UIKit框架(四十七) —— 自定義Calendar Control的簡(jiǎn)單示例(一)
48. UIKit框架(四十八) —— 自定義Calendar Control的簡(jiǎn)單示例(二)
49. UIKit框架(四十九) —— UIVisualEffectView原理和簡(jiǎn)單使用(一)
源碼
1. Swift
首先看下工程組織結(jié)構(gòu):
下面就是源碼啦
1. ThemedNavigationController.swift
import UIKit
class ThemedNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter
.default
.addObserver(
self,
selector: #selector(applyTheme),
name: themeDidChangeNotification,
object: nil)
}
@objc func applyTheme() {
let theme = Theme.shared
navigationBar.barTintColor = theme.barTintColor
view.tintColor = theme.tintColor
}
}
2. StoryListViewController.swift
import UIKit
class StoryListViewController: UITableViewController {
private var stories: [Story] = [] {
didSet {
tableView.reloadData()
}
}
@IBSegueAction func makeStoryViewController(_ coder: NSCoder) -> StoryViewController? {
guard let row = tableView.indexPathForSelectedRow?.row else {
return nil
}
let story = stories[row]
return StoryViewController(story: story, coder: coder)
}
}
// MARK: - Lifecycle
extension StoryListViewController {
override func viewDidLoad() {
super.viewDidLoad()
registerForNotifications()
let image = UIImage(systemName: "book.circle.fill")
let imageView = UIImageView(image: image)
imageView.frame.size = CGSize(width: 32, height: 32)
navigationItem.titleView = imageView
applyTheme()
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 79
Story.loadStories { [unowned self] loadedStories in
self.stories = loadedStories
}
}
}
// MARK: - Privates
private extension StoryListViewController {
func registerForNotifications() {
let notificationCenter = NotificationCenter.default
notificationCenter
.addObserver(
self,
selector: #selector(StoryViewController.preferredContentSizeDidChange(forChildContentContainer:)),
name: UIContentSizeCategory.didChangeNotification,
object: nil)
notificationCenter
.addObserver(
self,
selector: #selector(applyTheme),
name: themeDidChangeNotification,
object: nil)
}
@objc func applyTheme() {
tableView.separatorColor = Theme.shared.separatorColor
tableView.reloadData()
}
}
// MARK: - UITableViewDataSource
extension StoryListViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stories.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let storyCell = tableView.dequeueReusableCell(withIdentifier: "StoryCell", for: indexPath) as? StoryCell
storyCell?.story = stories[indexPath.row]
return storyCell ?? UITableViewCell()
}
}
3. StoryViewController.swift
import UIKit
class StoryViewController: UIViewController {
@IBOutlet weak var storyView: StoryView!
@IBOutlet weak var optionsContainerView: UIView!
@IBOutlet weak var optionsContainerViewBottomConstraint: NSLayoutConstraint!
private var showingOptions = false
private let story: Story
required init?(coder: NSCoder) {
fatalError("init(coder:) is not implemented")
}
init?(story: Story, coder: NSCoder) {
self.story = story
super.init(coder: coder)
}
}
// MARK: - Lifecycle
extension StoryViewController {
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(systemName: "book.circle.fill")
let imageView = UIImageView(image: image)
imageView.frame.size = CGSize(width: 32, height: 32)
navigationItem.titleView = imageView
storyView.story = story
applyTheme()
NotificationCenter
.default
.addObserver(
self,
selector: #selector(applyTheme),
name: themeDidChangeNotification,
object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setOptionsHidden(true, animated: false)
}
}
// MARK: - Privates
private extension StoryViewController {
@IBAction func optionsButtonTapped() {
setOptionsHidden(showingOptions, animated: true)
}
func setOptionsHidden(_ hidden: Bool, animated: Bool) {
showingOptions = !hidden
let height = optionsContainerView.bounds.height
let constant = hidden ? -height : view.safeAreaInsets.bottom
view.layoutIfNeeded()
optionsContainerViewBottomConstraint.constant = constant
if animated {
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
}
@objc func applyTheme() {
let theme = Theme.shared
view.backgroundColor = theme.textBackgroundColor
children.forEach { viewController in
viewController.view.tintColor = theme.tintColor
}
storyView.applyTheme()
}
}
4. OptionsViewController.swift
import UIKit
class OptionsViewController: UIViewController {
private var currentPage = 0
@IBOutlet weak var readingModeSegmentedControl: UISegmentedControl!
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var titleAlignmentSegmentedControl: UISegmentedControl!
@IBOutlet weak var pageControl: UIPageControl!
@IBOutlet weak var optionsView: UIView!
}
// MARK: - Lifecycle
extension OptionsViewController {
override func viewDidLoad() {
super.viewDidLoad()
scrollView.scrollsToTop = false
guard !UIAccessibility.isReduceTransparencyEnabled else {
return
}
view.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .dark)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
view.insertSubview(blurView, at: 0)
NSLayoutConstraint.activate([
blurView.topAnchor.constraint(equalTo: view.topAnchor),
blurView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
blurView.heightAnchor.constraint(equalTo: view.heightAnchor),
blurView.widthAnchor.constraint(equalTo: view.widthAnchor)
])
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
let vibrancyView = UIVisualEffectView(effect: vibrancyEffect)
vibrancyView.translatesAutoresizingMaskIntoConstraints = false
vibrancyView.contentView.addSubview(optionsView)
blurView.contentView.addSubview(vibrancyView)
NSLayoutConstraint.activate([
vibrancyView.heightAnchor.constraint(equalTo: blurView.contentView.heightAnchor),
vibrancyView.widthAnchor.constraint(equalTo: blurView.contentView.widthAnchor),
vibrancyView.centerXAnchor.constraint(equalTo: blurView.contentView.centerXAnchor),
vibrancyView.centerYAnchor.constraint(equalTo: blurView.contentView.centerYAnchor)
])
NSLayoutConstraint.activate([
optionsView.centerXAnchor.constraint(equalTo: vibrancyView.contentView.centerXAnchor),
optionsView.centerYAnchor.constraint(equalTo: vibrancyView.contentView.centerYAnchor)
])
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let theme = Theme.shared
readingModeSegmentedControl.selectedSegmentIndex = theme.readingMode.rawValue
titleAlignmentSegmentedControl.selectedSegmentIndex = theme.titleAlignment.rawValue
currentPage = theme.font.rawValue
pageControl.currentPage = currentPage
synchronizeViews(scrolled: false)
}
}
// MARK: - Actions
extension OptionsViewController {
@IBAction func pageControlPageDidChange() {
synchronizeViews(scrolled: false)
}
@IBAction func readingModeDidChange(_ segmentedControl: UISegmentedControl) {
Theme.shared.readingMode = ReadingMode(rawValue: segmentedControl.selectedSegmentIndex) ?? .dayTime
}
@IBAction func titleAlignmentDidChange(_ segmentedControl: UISegmentedControl) {
Theme.shared.titleAlignment = TitleAlignment(rawValue: segmentedControl.selectedSegmentIndex) ?? .left
}
private func synchronizeViews(scrolled: Bool) {
let pageWidth = scrollView.bounds.width
var page: Int = 0
var offset: CGFloat = 0
if scrolled {
offset = scrollView.contentOffset.x
page = Int(offset / pageWidth)
pageControl.currentPage = page
} else {
page = pageControl.currentPage
offset = CGFloat(page) * pageWidth
scrollView.setContentOffset(CGPoint(x: offset, y: 0), animated: true)
}
if page != currentPage {
currentPage = page
Theme.shared.font = Font(rawValue: currentPage) ?? .firaSans
}
}
}
// MARK: - UIScrollViewDelegate
extension OptionsViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.isDragging || scrollView.isDecelerating {
synchronizeViews(scrolled: true)
}
}
}
5. StoryCell.swift
import UIKit
class StoryCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var previewLabel: UILabel!
var story: Story? {
didSet {
guard let story = story else { return }
titleLabel.text = story.title
previewLabel.text = story.content
applyTheme()
}
}
func applyTheme() {
let theme = Theme.shared
backgroundColor = theme.textBackgroundColor
contentView.backgroundColor = theme.textBackgroundColor
titleLabel.backgroundColor = theme.textBackgroundColor
titleLabel.font = theme.preferredFont(forTextStyle: .headline)
titleLabel.textColor = theme.textColor
previewLabel.backgroundColor = theme.textBackgroundColor
previewLabel.font = theme.preferredFont(forTextStyle: .body)
previewLabel.textColor = theme.textColor
}
}
6. StoryView.swift
import UIKit
class StoryView: UIView {
@IBOutlet var titleLabel: UILabel!
@IBOutlet var contentLabel: UILabel!
@IBOutlet var separatorView: UIView!
var story: Story? {
didSet {
titleLabel.text = story?.title
contentLabel.text = story?.content
}
}
override func awakeFromNib() {
super.awakeFromNib()
applyTheme()
}
func applyTheme() {
let theme = Theme.shared
backgroundColor = theme.textBackgroundColor
titleLabel.backgroundColor = theme.textBackgroundColor
titleLabel.font = theme.preferredFont(forTextStyle: .headline)
titleLabel.textColor = theme.textColor
titleLabel.textAlignment = theme.titleAlignment == TitleAlignment.center
? NSTextAlignment.center : NSTextAlignment.left
contentLabel.backgroundColor = theme.textBackgroundColor
contentLabel.font = theme.preferredFont(forTextStyle: .body)
contentLabel.textColor = theme.textColor
separatorView.backgroundColor = theme.separatorColor
}
}
7. Theme.swift
import UIKit
let themeDidChangeNotification = Notification.Name("ThemeDidChangeNotification")
class Theme {
static var shared = Theme()
private let bodyFonts = [
"FiraSans-Regular",
"NotoSans",
"OpenSans",
"PTSans-Regular",
"SourceSansPro-Regular"
]
private let headlineFonts = [
"FiraSans-SemiBold",
"NotoSans-Bold",
"OpenSans-Semibold",
"PTSans-Bold",
"SourceSansPro-Semibold"
]
private let textBackgroundColors = [UIColor.white, UIColor.nightTimeTextBackground]
private let textColors = [UIColor.darkText, UIColor.nightTimeText]
var font: Font = .firaSans {
didSet {
notifyObservers()
}
}
var readingMode: ReadingMode = .dayTime {
didSet {
notifyObservers()
}
}
var titleAlignment: TitleAlignment = .center {
didSet {
notifyObservers()
}
}
var barTintColor: UIColor {
let color = textBackgroundColors[readingMode.rawValue]
return color.colorForTranslucency()
}
var tintColor: UIColor? {
return readingMode == .dayTime ? nil : UIColor.nightTimeTint
}
var separatorColor: UIColor {
return readingMode == .dayTime ? UIColor.defaultSeparator : UIColor.nightTimeTint
}
var textBackgroundColor: UIColor {
return textBackgroundColors[readingMode.rawValue]
}
var textColor: UIColor {
return textColors[readingMode.rawValue]
}
func preferredFont(forTextStyle style: UIFont.TextStyle) -> UIFont {
let systemFont = UIFont.preferredFont(forTextStyle: style)
if font == .system {
return systemFont
}
let size = systemFont.pointSize
var preferredFont: UIFont?
switch style {
case .headline:
preferredFont = UIFont(name: headlineFonts[font.rawValue], size: size)
default:
preferredFont = UIFont(name: bodyFonts[font.rawValue], size: size)
}
return preferredFont ?? systemFont
}
private func notifyObservers() {
NotificationCenter.default.post(name: themeDidChangeNotification, object: nil)
}
}
8. Font.swift
import Foundation
enum Font: Int {
case firaSans, notoSans, openSans, pztSans, sourceSansPro, system
}
9. ReadingMode.swift
import Foundation
enum ReadingMode: Int {
case dayTime, nightTime
}
10. TitleAlignment.swift
import Foundation
enum TitleAlignment: Int {
case center, left
}
11. Story.swift
import Foundation
struct Story {
let title: String
let content: String
static func loadStories(_ completion: (@escaping ([Story]) -> Void)) {
let path = Bundle.main.bundlePath
let manager = FileManager.default
var stories: [Story] = []
if var contents = try? manager.contentsOfDirectory(atPath: path) {
contents = contents.sorted(by: <)
for file in contents {
if file.hasSuffix(".grm") {
guard let filePath = URL(string: "file://" + path)?.appendingPathComponent(file) else { continue }
let title = String(file.split(separator: ".", maxSplits: 1, omittingEmptySubsequences: true)[0])
if let content = try? NSString(contentsOf: filePath, encoding: String.Encoding.utf8.rawValue) {
let story = Story(title: title, content: content as String)
stories.append(story)
}
}
}
}
DispatchQueue.main.async {
completion(stories)
}
}
}
12. UIColor+Extensions.swift
import UIKit
extension UIColor {
func colorForTranslucency() -> UIColor {
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
}
class func rgba(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) -> UIColor {
return UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: alpha)
}
class var defaultSeparator: UIColor {
return .rgba(red: 200, green: 199, blue: 204, alpha: 1)
}
class var nightTimeTextBackground: UIColor {
return .rgba(red: 245, green: 238, blue: 220, alpha: 1)
}
class var nightTimeText: UIColor {
return .rgba(red: 50, green: 20, blue: 0, alpha: 1)
}
class var nightTimeTint: UIColor {
return .rgba(red: 182, green: 126, blue: 44, alpha: 1)
}
}
后記
本篇主要講述了
UIVisualEffectView
原理和簡(jiǎn)單使用,感興趣的給個(gè)贊或者關(guān)注~~~