版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.11.29 星期日 |
前言
iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定义viewController的转场和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定义viewController的转场和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定义布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定义布局 (二)
28. UIKit框架(二十八) —— 一个UISplitViewController的简单实用示例 (一)
29. UIKit框架(二十九) —— 一个UISplitViewController的简单实用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(二)
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转盘效果的实现(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用协议构建自定义Collection(一)
42. UIKit框架(四十二) —— 使用协议构建自定义Collection(二)
43. UIKit框架(四十三) —— CALayer的简单实用示例(一)
44. UIKit框架(四十四) —— CALayer的简单实用示例(二)
45. UIKit框架(四十五) —— 支持DarkMode的简单示例(一)
46. UIKit框架(四十六) —— 支持DarkMode的简单示例(二)
47. UIKit框架(四十七) —— 自定义Calendar Control的简单示例(一)
48. UIKit框架(四十八) —— 自定义Calendar Control的简单示例(二)
49. UIKit框架(四十九) —— UIVisualEffectView原理和简单使用(一)
源码
1. Swift
首先看下工程组织结构:
下面就是源码啦
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
原理和简单使用,感兴趣的给个赞或者关注~~~
网友评论