美文网首页移动端开发
UIKit框架(五十) —— UIVisualEffectVie

UIKit框架(五十) —— UIVisualEffectVie

作者: 刀客传奇 | 来源:发表于2020-11-29 13:48 被阅读0次

    版本记录

    版本号 时间
    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原理和简单使用,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:UIKit框架(五十) —— UIVisualEffectVie

        本文链接:https://www.haomeiwen.com/subject/zxhkwktx.html