UIKit框架(五十) —— UIVisualEffectView原理和簡(jiǎn)單使用(二)

版本記錄

版本號(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)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容