├── TB_TwitterHeader
├── TB_TwitterHeader-Bridging-Header.h
├── Images.xcassets
│ ├── t&b.imageset
│ │ ├── t&b@2x.png
│ │ └── Contents.json
│ ├── love.imageset
│ │ ├── love@2x.png
│ │ └── Contents.json
│ ├── profile.imageset
│ │ ├── profile@2x.jpg
│ │ └── Contents.json
│ ├── header_bg.imageset
│ │ ├── header_bg@2x.jpg
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── AvatarImageView.swift
├── TWTButton.swift
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── AppDelegate.swift
├── FXBlurView.h
├── ViewController.swift
└── FXBlurView.m
├── README.md
└── TB_TwitterHeader.xcodeproj
├── xcuserdata
└── yari.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ ├── xcschememanagement.plist
│ └── TB_TwitterHeader.xcscheme
├── project.xcworkspace
├── contents.xcworkspacedata
└── xcuserdata
│ └── yari.xcuserdatad
│ └── UserInterfaceState.xcuserstate
└── project.pbxproj
/TB_TwitterHeader/TB_TwitterHeader-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "FXBlurView.h"
2 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/t&b.imageset/t&b@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ariok/TB_TwitterUI/HEAD/TB_TwitterHeader/Images.xcassets/t&b.imageset/t&b@2x.png
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/love.imageset/love@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ariok/TB_TwitterUI/HEAD/TB_TwitterHeader/Images.xcassets/love.imageset/love@2x.png
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/profile.imageset/profile@2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ariok/TB_TwitterUI/HEAD/TB_TwitterHeader/Images.xcassets/profile.imageset/profile@2x.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TB_TwitterUI
2 | This is the code for the ThinkAndBuild tutorial: ["Implementing the Twitter App UI"](http://www.thinkandbuild.it/implementing-the-twitter-ios-app-ui/)
3 |
4 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/header_bg.imageset/header_bg@2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ariok/TB_TwitterUI/HEAD/TB_TwitterHeader/Images.xcassets/header_bg.imageset/header_bg@2x.jpg
--------------------------------------------------------------------------------
/TB_TwitterHeader.xcodeproj/xcuserdata/yari.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/TB_TwitterHeader.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TB_TwitterHeader.xcodeproj/project.xcworkspace/xcuserdata/yari.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ariok/TB_TwitterUI/HEAD/TB_TwitterHeader.xcodeproj/project.xcworkspace/xcuserdata/yari.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/TB_TwitterHeader/AvatarImageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AvatarImageView.swift
3 | // AvatarImageView
4 | //
5 | // Created by Yari D'areglia on 08/12/2016.
6 |
7 | import UIKit
8 |
9 | class AvatarImageView: UIImageView {
10 |
11 | override func awakeFromNib() {
12 | self.layer.cornerRadius = 10.0
13 | self.layer.borderColor = UIColor.white.cgColor
14 | self.layer.borderWidth = 3.0
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/t&b.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x",
10 | "filename" : "t&b@2x.png"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/love.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x",
10 | "filename" : "love@2x.png"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x",
10 | "filename" : "profile@2x.jpg"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/header_bg.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x",
10 | "filename" : "header_bg@2x.jpg"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/TB_TwitterHeader/TWTButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TWTButton.swift
3 | // TB_TwitterHeader
4 | //
5 | // Created by Yari D'areglia on 08/12/2016.
6 |
7 | import UIKit
8 |
9 | class TWTButton: UIButton {
10 |
11 | override func awakeFromNib() {
12 |
13 | self.layer.cornerRadius = 5.0
14 | self.layer.borderWidth = 1.0
15 | self.layer.borderColor = UIColor(red: 85.0/255.0, green: 172.0/255.0, blue: 238.0/255.0, alpha: 1.0).cgColor
16 |
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/TB_TwitterHeader.xcodeproj/xcuserdata/yari.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | TB_TwitterHeader.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 58B1125E1DFA11DA000162BD
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | }
43 | ],
44 | "info" : {
45 | "version" : 1,
46 | "author" : "xcode"
47 | }
48 | }
--------------------------------------------------------------------------------
/TB_TwitterHeader/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 3.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TB_TwitterHeader
4 | //
5 | // Created by Yari D'areglia on 08/12/2016.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | func applicationWillResignActive(_ application: UIApplication) {
22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
24 | }
25 |
26 | func applicationDidEnterBackground(_ application: UIApplication) {
27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29 | }
30 |
31 | func applicationWillEnterForeground(_ application: UIApplication) {
32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
33 | }
34 |
35 | func applicationDidBecomeActive(_ application: UIApplication) {
36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
37 | }
38 |
39 | func applicationWillTerminate(_ application: UIApplication) {
40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
41 | }
42 |
43 |
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/FXBlurView.h:
--------------------------------------------------------------------------------
1 | //
2 | // FXBlurView.h
3 | //
4 | // Version 1.6.3
5 | //
6 | // Created by Nick Lockwood on 25/08/2013.
7 | // Copyright (c) 2013 Charcoal Design
8 | //
9 | // Distributed under the permissive zlib License
10 | // Get the latest version from here:
11 | //
12 | // https://github.com/nicklockwood/FXBlurView
13 | //
14 | // This software is provided 'as-is', without any express or implied
15 | // warranty. In no event will the authors be held liable for any damages
16 | // arising from the use of this software.
17 | //
18 | // Permission is granted to anyone to use this software for any purpose,
19 | // including commercial applications, and to alter it and redistribute it
20 | // freely, subject to the following restrictions:
21 | //
22 | // 1. The origin of this software must not be misrepresented; you must not
23 | // claim that you wrote the original software. If you use this software
24 | // in a product, an acknowledgment in the product documentation would be
25 | // appreciated but is not required.
26 | //
27 | // 2. Altered source versions must be plainly marked as such, and must not be
28 | // misrepresented as being the original software.
29 | //
30 | // 3. This notice may not be removed or altered from any source distribution.
31 | //
32 |
33 |
34 | #import
35 | #import
36 | #import
37 |
38 |
39 | #pragma GCC diagnostic push
40 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
41 |
42 |
43 | #import
44 | #undef weak_ref
45 | #if __has_feature(objc_arc) && __has_feature(objc_arc_weak)
46 | #define weak_ref weak
47 | #else
48 | #define weak_ref unsafe_unretained
49 | #endif
50 |
51 | @interface UIImage (FXBlurView)
52 |
53 | - (UIImage *)blurredImageWithRadius:(CGFloat)radius iterations:(NSUInteger)iterations tintColor:(UIColor *)tintColor;
54 |
55 | @end
56 |
57 |
58 | @interface FXBlurView : UIView
59 |
60 | + (void)setBlurEnabled:(BOOL)blurEnabled;
61 | + (void)setUpdatesEnabled;
62 | + (void)setUpdatesDisabled;
63 |
64 | @property (nonatomic, getter = isBlurEnabled) BOOL blurEnabled;
65 | @property (nonatomic, getter = isDynamic) BOOL dynamic;
66 | @property (nonatomic, assign) NSUInteger iterations;
67 | @property (nonatomic, assign) NSTimeInterval updateInterval;
68 | @property (nonatomic, assign) CGFloat blurRadius;
69 | @property (nonatomic, strong) UIColor *tintColor;
70 | @property (nonatomic, weak_ref) IBOutlet UIView *underlyingView;
71 |
72 | - (void)updateAsynchronously:(BOOL)async completion:(void (^)())completion;
73 |
74 | @end
75 |
76 |
77 | #pragma GCC diagnostic pop
78 |
79 |
--------------------------------------------------------------------------------
/TB_TwitterHeader.xcodeproj/xcuserdata/yari.xcuserdatad/xcschemes/TB_TwitterHeader.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // TB_TwitterHeader
4 | //
5 | // Created by Yari D'areglia on 08/12/2016.
6 |
7 | import UIKit
8 |
9 | let offset_HeaderStop:CGFloat = 40.0 // At this offset the Header stops its transformations
10 | let offset_B_LabelHeader:CGFloat = 95.0 // At this offset the Black label reaches the Header
11 | let distance_W_LabelHeader:CGFloat = 35.0 // The distance between the bottom of the Header and the top of the White Label
12 |
13 | class ViewController: UIViewController, UIScrollViewDelegate {
14 |
15 | @IBOutlet var scrollView:UIScrollView!
16 | @IBOutlet var avatarImage:UIImageView!
17 | @IBOutlet var header:UIView!
18 | @IBOutlet var headerLabel:UILabel!
19 | @IBOutlet var headerImageView:UIImageView!
20 | @IBOutlet var headerBlurImageView:UIImageView!
21 | var blurredHeaderImageView:UIImageView?
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 | scrollView.delegate = self
26 | }
27 |
28 | override func viewDidAppear(_ animated: Bool) {
29 |
30 | // Header - Image
31 |
32 | headerImageView = UIImageView(frame: header.bounds)
33 | headerImageView?.image = UIImage(named: "header_bg")
34 | headerImageView?.contentMode = UIViewContentMode.scaleAspectFill
35 | header.insertSubview(headerImageView, belowSubview: headerLabel)
36 |
37 | // Header - Blurred Image
38 |
39 | headerBlurImageView = UIImageView(frame: header.bounds)
40 | headerBlurImageView?.image = UIImage(named: "header_bg")?.blurredImage(withRadius: 10, iterations: 20, tintColor: UIColor.clear)
41 | headerBlurImageView?.contentMode = UIViewContentMode.scaleAspectFill
42 | headerBlurImageView?.alpha = 0.0
43 | header.insertSubview(headerBlurImageView, belowSubview: headerLabel)
44 |
45 | header.clipsToBounds = true
46 | }
47 |
48 | override func didReceiveMemoryWarning() {
49 | super.didReceiveMemoryWarning()
50 | }
51 |
52 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
53 |
54 | let offset = scrollView.contentOffset.y
55 | var avatarTransform = CATransform3DIdentity
56 | var headerTransform = CATransform3DIdentity
57 |
58 | // PULL DOWN -----------------
59 |
60 | if offset < 0 {
61 |
62 | let headerScaleFactor:CGFloat = -(offset) / header.bounds.height
63 | let headerSizevariation = ((header.bounds.height * (1.0 + headerScaleFactor)) - header.bounds.height)/2.0
64 | headerTransform = CATransform3DTranslate(headerTransform, 0, headerSizevariation, 0)
65 | headerTransform = CATransform3DScale(headerTransform, 1.0 + headerScaleFactor, 1.0 + headerScaleFactor, 0)
66 |
67 | header.layer.transform = headerTransform
68 | }
69 |
70 | // SCROLL UP/DOWN ------------
71 |
72 | else {
73 |
74 | // Header -----------
75 |
76 | headerTransform = CATransform3DTranslate(headerTransform, 0, max(-offset_HeaderStop, -offset), 0)
77 |
78 | // ------------ Label
79 |
80 | let labelTransform = CATransform3DMakeTranslation(0, max(-distance_W_LabelHeader, offset_B_LabelHeader - offset), 0)
81 | headerLabel.layer.transform = labelTransform
82 |
83 | // ------------ Blur
84 |
85 | headerBlurImageView?.alpha = min (1.0, (offset - offset_B_LabelHeader)/distance_W_LabelHeader)
86 |
87 | // Avatar -----------
88 |
89 | let avatarScaleFactor = (min(offset_HeaderStop, offset)) / avatarImage.bounds.height / 1.4 // Slow down the animation
90 | let avatarSizeVariation = ((avatarImage.bounds.height * (1.0 + avatarScaleFactor)) - avatarImage.bounds.height) / 2.0
91 | avatarTransform = CATransform3DTranslate(avatarTransform, 0, avatarSizeVariation, 0)
92 | avatarTransform = CATransform3DScale(avatarTransform, 1.0 - avatarScaleFactor, 1.0 - avatarScaleFactor, 0)
93 |
94 | if offset <= offset_HeaderStop {
95 |
96 | if avatarImage.layer.zPosition < header.layer.zPosition{
97 | header.layer.zPosition = 0
98 | }
99 |
100 | }else {
101 | if avatarImage.layer.zPosition >= header.layer.zPosition{
102 | header.layer.zPosition = 2
103 | }
104 | }
105 | }
106 |
107 | // Apply Transformations
108 |
109 | header.layer.transform = headerTransform
110 | avatarImage.layer.transform = avatarTransform
111 | }
112 |
113 | @IBAction func shamelessActionThatBringsYouToMyTwitterProfile() {
114 | if UIApplication.shared.canOpenURL(URL(string:"twitter://user?screen_name=bitwaker")!){
115 | UIApplication.shared.open(URL(string:"twitter://user?screen_name=bitwaker")!)
116 | }else{
117 | UIApplication.shared.open(URL(string:"https://twitter.com/bitwaker")!)
118 | }
119 | }
120 |
121 | override var preferredStatusBarStyle: UIStatusBarStyle{
122 | return UIStatusBarStyle.lightContent
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
55 |
69 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/TB_TwitterHeader.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 583C54FF1DFA15F80087D73C /* FXBlurView.m in Sources */ = {isa = PBXBuildFile; fileRef = 583C54FE1DFA15F80087D73C /* FXBlurView.m */; };
11 | 58B112631DFA11DB000162BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B112621DFA11DB000162BD /* AppDelegate.swift */; };
12 | 58B112651DFA11DB000162BD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B112641DFA11DB000162BD /* ViewController.swift */; };
13 | 58B112681DFA11DB000162BD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 58B112661DFA11DB000162BD /* Main.storyboard */; };
14 | 58B1126D1DFA11DB000162BD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 58B1126B1DFA11DB000162BD /* LaunchScreen.storyboard */; };
15 | 58B1127A1DFA1293000162BD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 58B112791DFA1293000162BD /* Images.xcassets */; };
16 | 58B1127C1DFA12C8000162BD /* TWTButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B1127B1DFA12C8000162BD /* TWTButton.swift */; };
17 | 58B1127E1DFA131E000162BD /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B1127D1DFA131E000162BD /* AvatarImageView.swift */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXFileReference section */
21 | 583C54FD1DFA15F80087D73C /* FXBlurView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FXBlurView.h; sourceTree = ""; };
22 | 583C54FE1DFA15F80087D73C /* FXBlurView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FXBlurView.m; sourceTree = ""; };
23 | 58B1125F1DFA11DB000162BD /* TB_TwitterHeader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TB_TwitterHeader.app; sourceTree = BUILT_PRODUCTS_DIR; };
24 | 58B112621DFA11DB000162BD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
25 | 58B112641DFA11DB000162BD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
26 | 58B112671DFA11DB000162BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
27 | 58B1126C1DFA11DB000162BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 58B1126E1DFA11DB000162BD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 58B112751DFA1232000162BD /* TB_TwitterHeader-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TB_TwitterHeader-Bridging-Header.h"; sourceTree = ""; };
30 | 58B112791DFA1293000162BD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
31 | 58B1127B1DFA12C8000162BD /* TWTButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TWTButton.swift; sourceTree = ""; };
32 | 58B1127D1DFA131E000162BD /* AvatarImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = ""; };
33 | /* End PBXFileReference section */
34 |
35 | /* Begin PBXFrameworksBuildPhase section */
36 | 58B1125C1DFA11DA000162BD /* Frameworks */ = {
37 | isa = PBXFrameworksBuildPhase;
38 | buildActionMask = 2147483647;
39 | files = (
40 | );
41 | runOnlyForDeploymentPostprocessing = 0;
42 | };
43 | /* End PBXFrameworksBuildPhase section */
44 |
45 | /* Begin PBXGroup section */
46 | 58B112561DFA11DA000162BD = {
47 | isa = PBXGroup;
48 | children = (
49 | 58B112611DFA11DB000162BD /* TB_TwitterHeader */,
50 | 58B112601DFA11DB000162BD /* Products */,
51 | );
52 | sourceTree = "";
53 | };
54 | 58B112601DFA11DB000162BD /* Products */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 58B1125F1DFA11DB000162BD /* TB_TwitterHeader.app */,
58 | );
59 | name = Products;
60 | sourceTree = "";
61 | };
62 | 58B112611DFA11DB000162BD /* TB_TwitterHeader */ = {
63 | isa = PBXGroup;
64 | children = (
65 | 58B112741DFA121E000162BD /* Helpers */,
66 | 58B112621DFA11DB000162BD /* AppDelegate.swift */,
67 | 58B112641DFA11DB000162BD /* ViewController.swift */,
68 | 58B112661DFA11DB000162BD /* Main.storyboard */,
69 | 58B112791DFA1293000162BD /* Images.xcassets */,
70 | 58B1126B1DFA11DB000162BD /* LaunchScreen.storyboard */,
71 | 58B1126E1DFA11DB000162BD /* Info.plist */,
72 | );
73 | path = TB_TwitterHeader;
74 | sourceTree = "";
75 | };
76 | 58B112741DFA121E000162BD /* Helpers */ = {
77 | isa = PBXGroup;
78 | children = (
79 | 583C54FD1DFA15F80087D73C /* FXBlurView.h */,
80 | 583C54FE1DFA15F80087D73C /* FXBlurView.m */,
81 | 58B1127D1DFA131E000162BD /* AvatarImageView.swift */,
82 | 58B1127B1DFA12C8000162BD /* TWTButton.swift */,
83 | 58B112751DFA1232000162BD /* TB_TwitterHeader-Bridging-Header.h */,
84 | );
85 | name = Helpers;
86 | sourceTree = "";
87 | };
88 | /* End PBXGroup section */
89 |
90 | /* Begin PBXNativeTarget section */
91 | 58B1125E1DFA11DA000162BD /* TB_TwitterHeader */ = {
92 | isa = PBXNativeTarget;
93 | buildConfigurationList = 58B112711DFA11DB000162BD /* Build configuration list for PBXNativeTarget "TB_TwitterHeader" */;
94 | buildPhases = (
95 | 58B1125B1DFA11DA000162BD /* Sources */,
96 | 58B1125C1DFA11DA000162BD /* Frameworks */,
97 | 58B1125D1DFA11DA000162BD /* Resources */,
98 | );
99 | buildRules = (
100 | );
101 | dependencies = (
102 | );
103 | name = TB_TwitterHeader;
104 | productName = TB_TwitterHeader;
105 | productReference = 58B1125F1DFA11DB000162BD /* TB_TwitterHeader.app */;
106 | productType = "com.apple.product-type.application";
107 | };
108 | /* End PBXNativeTarget section */
109 |
110 | /* Begin PBXProject section */
111 | 58B112571DFA11DA000162BD /* Project object */ = {
112 | isa = PBXProject;
113 | attributes = {
114 | LastSwiftUpdateCheck = 0810;
115 | LastUpgradeCheck = 0810;
116 | ORGANIZATIONNAME = Bitwaker;
117 | TargetAttributes = {
118 | 58B1125E1DFA11DA000162BD = {
119 | CreatedOnToolsVersion = 8.1;
120 | DevelopmentTeam = T5V89Y6UVF;
121 | LastSwiftMigration = 0810;
122 | ProvisioningStyle = Automatic;
123 | };
124 | };
125 | };
126 | buildConfigurationList = 58B1125A1DFA11DA000162BD /* Build configuration list for PBXProject "TB_TwitterHeader" */;
127 | compatibilityVersion = "Xcode 3.2";
128 | developmentRegion = English;
129 | hasScannedForEncodings = 0;
130 | knownRegions = (
131 | en,
132 | Base,
133 | );
134 | mainGroup = 58B112561DFA11DA000162BD;
135 | productRefGroup = 58B112601DFA11DB000162BD /* Products */;
136 | projectDirPath = "";
137 | projectRoot = "";
138 | targets = (
139 | 58B1125E1DFA11DA000162BD /* TB_TwitterHeader */,
140 | );
141 | };
142 | /* End PBXProject section */
143 |
144 | /* Begin PBXResourcesBuildPhase section */
145 | 58B1125D1DFA11DA000162BD /* Resources */ = {
146 | isa = PBXResourcesBuildPhase;
147 | buildActionMask = 2147483647;
148 | files = (
149 | 58B1126D1DFA11DB000162BD /* LaunchScreen.storyboard in Resources */,
150 | 58B1127A1DFA1293000162BD /* Images.xcassets in Resources */,
151 | 58B112681DFA11DB000162BD /* Main.storyboard in Resources */,
152 | );
153 | runOnlyForDeploymentPostprocessing = 0;
154 | };
155 | /* End PBXResourcesBuildPhase section */
156 |
157 | /* Begin PBXSourcesBuildPhase section */
158 | 58B1125B1DFA11DA000162BD /* Sources */ = {
159 | isa = PBXSourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 58B112651DFA11DB000162BD /* ViewController.swift in Sources */,
163 | 58B1127E1DFA131E000162BD /* AvatarImageView.swift in Sources */,
164 | 583C54FF1DFA15F80087D73C /* FXBlurView.m in Sources */,
165 | 58B1127C1DFA12C8000162BD /* TWTButton.swift in Sources */,
166 | 58B112631DFA11DB000162BD /* AppDelegate.swift in Sources */,
167 | );
168 | runOnlyForDeploymentPostprocessing = 0;
169 | };
170 | /* End PBXSourcesBuildPhase section */
171 |
172 | /* Begin PBXVariantGroup section */
173 | 58B112661DFA11DB000162BD /* Main.storyboard */ = {
174 | isa = PBXVariantGroup;
175 | children = (
176 | 58B112671DFA11DB000162BD /* Base */,
177 | );
178 | name = Main.storyboard;
179 | sourceTree = "";
180 | };
181 | 58B1126B1DFA11DB000162BD /* LaunchScreen.storyboard */ = {
182 | isa = PBXVariantGroup;
183 | children = (
184 | 58B1126C1DFA11DB000162BD /* Base */,
185 | );
186 | name = LaunchScreen.storyboard;
187 | sourceTree = "";
188 | };
189 | /* End PBXVariantGroup section */
190 |
191 | /* Begin XCBuildConfiguration section */
192 | 58B1126F1DFA11DB000162BD /* Debug */ = {
193 | isa = XCBuildConfiguration;
194 | buildSettings = {
195 | ALWAYS_SEARCH_USER_PATHS = NO;
196 | CLANG_ANALYZER_NONNULL = YES;
197 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
198 | CLANG_CXX_LIBRARY = "libc++";
199 | CLANG_ENABLE_MODULES = YES;
200 | CLANG_ENABLE_OBJC_ARC = YES;
201 | CLANG_WARN_BOOL_CONVERSION = YES;
202 | CLANG_WARN_CONSTANT_CONVERSION = YES;
203 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
204 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
205 | CLANG_WARN_EMPTY_BODY = YES;
206 | CLANG_WARN_ENUM_CONVERSION = YES;
207 | CLANG_WARN_INFINITE_RECURSION = YES;
208 | CLANG_WARN_INT_CONVERSION = YES;
209 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
210 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
211 | CLANG_WARN_UNREACHABLE_CODE = YES;
212 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
213 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
214 | COPY_PHASE_STRIP = NO;
215 | DEBUG_INFORMATION_FORMAT = dwarf;
216 | ENABLE_STRICT_OBJC_MSGSEND = YES;
217 | ENABLE_TESTABILITY = YES;
218 | GCC_C_LANGUAGE_STANDARD = gnu99;
219 | GCC_DYNAMIC_NO_PIC = NO;
220 | GCC_NO_COMMON_BLOCKS = YES;
221 | GCC_OPTIMIZATION_LEVEL = 0;
222 | GCC_PREPROCESSOR_DEFINITIONS = (
223 | "DEBUG=1",
224 | "$(inherited)",
225 | );
226 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
227 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
228 | GCC_WARN_UNDECLARED_SELECTOR = YES;
229 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
230 | GCC_WARN_UNUSED_FUNCTION = YES;
231 | GCC_WARN_UNUSED_VARIABLE = YES;
232 | IPHONEOS_DEPLOYMENT_TARGET = 10.1;
233 | MTL_ENABLE_DEBUG_INFO = YES;
234 | ONLY_ACTIVE_ARCH = YES;
235 | SDKROOT = iphoneos;
236 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
237 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
238 | TARGETED_DEVICE_FAMILY = "1,2";
239 | };
240 | name = Debug;
241 | };
242 | 58B112701DFA11DB000162BD /* Release */ = {
243 | isa = XCBuildConfiguration;
244 | buildSettings = {
245 | ALWAYS_SEARCH_USER_PATHS = NO;
246 | CLANG_ANALYZER_NONNULL = YES;
247 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
248 | CLANG_CXX_LIBRARY = "libc++";
249 | CLANG_ENABLE_MODULES = YES;
250 | CLANG_ENABLE_OBJC_ARC = YES;
251 | CLANG_WARN_BOOL_CONVERSION = YES;
252 | CLANG_WARN_CONSTANT_CONVERSION = YES;
253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
255 | CLANG_WARN_EMPTY_BODY = YES;
256 | CLANG_WARN_ENUM_CONVERSION = YES;
257 | CLANG_WARN_INFINITE_RECURSION = YES;
258 | CLANG_WARN_INT_CONVERSION = YES;
259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
260 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
261 | CLANG_WARN_UNREACHABLE_CODE = YES;
262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
263 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
264 | COPY_PHASE_STRIP = NO;
265 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
266 | ENABLE_NS_ASSERTIONS = NO;
267 | ENABLE_STRICT_OBJC_MSGSEND = YES;
268 | GCC_C_LANGUAGE_STANDARD = gnu99;
269 | GCC_NO_COMMON_BLOCKS = YES;
270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
272 | GCC_WARN_UNDECLARED_SELECTOR = YES;
273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
274 | GCC_WARN_UNUSED_FUNCTION = YES;
275 | GCC_WARN_UNUSED_VARIABLE = YES;
276 | IPHONEOS_DEPLOYMENT_TARGET = 10.1;
277 | MTL_ENABLE_DEBUG_INFO = NO;
278 | SDKROOT = iphoneos;
279 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
280 | TARGETED_DEVICE_FAMILY = "1,2";
281 | VALIDATE_PRODUCT = YES;
282 | };
283 | name = Release;
284 | };
285 | 58B112721DFA11DB000162BD /* Debug */ = {
286 | isa = XCBuildConfiguration;
287 | buildSettings = {
288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 | CLANG_ENABLE_MODULES = YES;
290 | DEVELOPMENT_TEAM = T5V89Y6UVF;
291 | INFOPLIST_FILE = TB_TwitterHeader/Info.plist;
292 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
293 | PRODUCT_BUNDLE_IDENTIFIER = "com.bitwaker.TB-TwitterHeader";
294 | PRODUCT_NAME = "$(TARGET_NAME)";
295 | SWIFT_OBJC_BRIDGING_HEADER = "TB_TwitterHeader/TB_TwitterHeader-Bridging-Header.h";
296 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
297 | SWIFT_VERSION = 3.0;
298 | };
299 | name = Debug;
300 | };
301 | 58B112731DFA11DB000162BD /* Release */ = {
302 | isa = XCBuildConfiguration;
303 | buildSettings = {
304 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
305 | CLANG_ENABLE_MODULES = YES;
306 | DEVELOPMENT_TEAM = T5V89Y6UVF;
307 | INFOPLIST_FILE = TB_TwitterHeader/Info.plist;
308 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
309 | PRODUCT_BUNDLE_IDENTIFIER = "com.bitwaker.TB-TwitterHeader";
310 | PRODUCT_NAME = "$(TARGET_NAME)";
311 | SWIFT_OBJC_BRIDGING_HEADER = "TB_TwitterHeader/TB_TwitterHeader-Bridging-Header.h";
312 | SWIFT_VERSION = 3.0;
313 | };
314 | name = Release;
315 | };
316 | /* End XCBuildConfiguration section */
317 |
318 | /* Begin XCConfigurationList section */
319 | 58B1125A1DFA11DA000162BD /* Build configuration list for PBXProject "TB_TwitterHeader" */ = {
320 | isa = XCConfigurationList;
321 | buildConfigurations = (
322 | 58B1126F1DFA11DB000162BD /* Debug */,
323 | 58B112701DFA11DB000162BD /* Release */,
324 | );
325 | defaultConfigurationIsVisible = 0;
326 | defaultConfigurationName = Release;
327 | };
328 | 58B112711DFA11DB000162BD /* Build configuration list for PBXNativeTarget "TB_TwitterHeader" */ = {
329 | isa = XCConfigurationList;
330 | buildConfigurations = (
331 | 58B112721DFA11DB000162BD /* Debug */,
332 | 58B112731DFA11DB000162BD /* Release */,
333 | );
334 | defaultConfigurationIsVisible = 0;
335 | defaultConfigurationName = Release;
336 | };
337 | /* End XCConfigurationList section */
338 | };
339 | rootObject = 58B112571DFA11DA000162BD /* Project object */;
340 | }
341 |
--------------------------------------------------------------------------------
/TB_TwitterHeader/FXBlurView.m:
--------------------------------------------------------------------------------
1 | //
2 | // FXBlurView.m
3 | //
4 | // Version 1.6.3
5 | //
6 | // Created by Nick Lockwood on 25/08/2013.
7 | // Copyright (c) 2013 Charcoal Design
8 | //
9 | // Distributed under the permissive zlib License
10 | // Get the latest version from here:
11 | //
12 | // https://github.com/nicklockwood/FXBlurView
13 | //
14 | // This software is provided 'as-is', without any express or implied
15 | // warranty. In no event will the authors be held liable for any damages
16 | // arising from the use of this software.
17 | //
18 | // Permission is granted to anyone to use this software for any purpose,
19 | // including commercial applications, and to alter it and redistribute it
20 | // freely, subject to the following restrictions:
21 | //
22 | // 1. The origin of this software must not be misrepresented; you must not
23 | // claim that you wrote the original software. If you use this software
24 | // in a product, an acknowledgment in the product documentation would be
25 | // appreciated but is not required.
26 | //
27 | // 2. Altered source versions must be plainly marked as such, and must not be
28 | // misrepresented as being the original software.
29 | //
30 | // 3. This notice may not be removed or altered from any source distribution.
31 | //
32 |
33 |
34 | #import "FXBlurView.h"
35 | #import
36 |
37 |
38 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
39 | #pragma GCC diagnostic ignored "-Wdirect-ivar-access"
40 | #pragma GCC diagnostic ignored "-Wgnu"
41 |
42 |
43 | #import
44 | #if !__has_feature(objc_arc)
45 | #error This class requires automatic reference counting
46 | #endif
47 |
48 | @implementation UIImage (FXBlurView)
49 |
50 | - (UIImage *)blurredImageWithRadius:(CGFloat)radius iterations:(NSUInteger)iterations tintColor:(UIColor *)tintColor
51 | {
52 | //image must be nonzero size
53 | if (floorf(self.size.width) * floorf(self.size.height) <= 0.0f) return self;
54 |
55 | //boxsize must be an odd integer
56 | uint32_t boxSize = (uint32_t)(radius * self.scale);
57 | if (boxSize % 2 == 0) boxSize ++;
58 |
59 | //create image buffers
60 | CGImageRef imageRef = self.CGImage;
61 | vImage_Buffer buffer1, buffer2;
62 | buffer1.width = buffer2.width = CGImageGetWidth(imageRef);
63 | buffer1.height = buffer2.height = CGImageGetHeight(imageRef);
64 | buffer1.rowBytes = buffer2.rowBytes = CGImageGetBytesPerRow(imageRef);
65 | size_t bytes = buffer1.rowBytes * buffer1.height;
66 | buffer1.data = malloc(bytes);
67 | buffer2.data = malloc(bytes);
68 |
69 | //create temp buffer
70 | void *tempBuffer = malloc((size_t)vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, NULL, 0, 0, boxSize, boxSize,
71 | NULL, kvImageEdgeExtend + kvImageGetTempBufferSize));
72 |
73 | //copy image data
74 | CFDataRef dataSource = CGDataProviderCopyData(CGImageGetDataProvider(imageRef));
75 | memcpy(buffer1.data, CFDataGetBytePtr(dataSource), bytes);
76 | CFRelease(dataSource);
77 |
78 | for (NSUInteger i = 0; i < iterations; i++)
79 | {
80 | //perform blur
81 | vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, tempBuffer, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
82 |
83 | //swap buffers
84 | void *temp = buffer1.data;
85 | buffer1.data = buffer2.data;
86 | buffer2.data = temp;
87 | }
88 |
89 | //free buffers
90 | free(buffer2.data);
91 | free(tempBuffer);
92 |
93 | //create image context from buffer
94 | CGContextRef ctx = CGBitmapContextCreate(buffer1.data, buffer1.width, buffer1.height,
95 | 8, buffer1.rowBytes, CGImageGetColorSpace(imageRef),
96 | CGImageGetBitmapInfo(imageRef));
97 |
98 | //apply tint
99 | if (tintColor && CGColorGetAlpha(tintColor.CGColor) > 0.0f)
100 | {
101 | CGContextSetFillColorWithColor(ctx, [tintColor colorWithAlphaComponent:0.25].CGColor);
102 | CGContextSetBlendMode(ctx, kCGBlendModePlusLighter);
103 | CGContextFillRect(ctx, CGRectMake(0, 0, buffer1.width, buffer1.height));
104 | }
105 |
106 | //create image from context
107 | imageRef = CGBitmapContextCreateImage(ctx);
108 | UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
109 | CGImageRelease(imageRef);
110 | CGContextRelease(ctx);
111 | free(buffer1.data);
112 | return image;
113 | }
114 |
115 | @end
116 |
117 |
118 | @interface FXBlurScheduler : NSObject
119 |
120 | @property (nonatomic, strong) NSMutableArray *views;
121 | @property (nonatomic, assign) NSUInteger viewIndex;
122 | @property (nonatomic, assign) NSUInteger updatesEnabled;
123 | @property (nonatomic, assign) BOOL blurEnabled;
124 | @property (nonatomic, assign) BOOL updating;
125 |
126 | @end
127 |
128 |
129 | @interface FXBlurLayer: CALayer
130 |
131 | @property (nonatomic, assign) CGFloat blurRadius;
132 |
133 | @end
134 |
135 |
136 | @implementation FXBlurLayer
137 |
138 | @dynamic blurRadius;
139 |
140 | + (BOOL)needsDisplayForKey:(NSString *)key
141 | {
142 | if ([@[@"blurRadius", @"bounds", @"position"] containsObject:key])
143 | {
144 | return YES;
145 | }
146 | return [super needsDisplayForKey:key];
147 | }
148 |
149 | @end
150 |
151 |
152 | @interface FXBlurView ()
153 |
154 | @property (nonatomic, assign) BOOL iterationsSet;
155 | @property (nonatomic, assign) BOOL blurRadiusSet;
156 | @property (nonatomic, assign) BOOL dynamicSet;
157 | @property (nonatomic, assign) BOOL blurEnabledSet;
158 | @property (nonatomic, strong) NSDate *lastUpdate;
159 |
160 | - (UIImage *)snapshotOfUnderlyingView;
161 | - (BOOL)shouldUpdate;
162 |
163 | @end
164 |
165 |
166 | @implementation FXBlurScheduler
167 |
168 | + (instancetype)sharedInstance
169 | {
170 | static FXBlurScheduler *sharedInstance = nil;
171 | if (!sharedInstance)
172 | {
173 | sharedInstance = [[FXBlurScheduler alloc] init];
174 | }
175 | return sharedInstance;
176 | }
177 |
178 | - (instancetype)init
179 | {
180 | if ((self = [super init]))
181 | {
182 | _updatesEnabled = 1;
183 | _blurEnabled = YES;
184 | _views = [[NSMutableArray alloc] init];
185 | }
186 | return self;
187 | }
188 |
189 | - (void)setBlurEnabled:(BOOL)blurEnabled
190 | {
191 | _blurEnabled = blurEnabled;
192 | if (blurEnabled)
193 | {
194 | for (FXBlurView *view in self.views)
195 | {
196 | [view setNeedsDisplay];
197 | }
198 | [self updateAsynchronously];
199 | }
200 | }
201 |
202 | - (void)setUpdatesEnabled
203 | {
204 | _updatesEnabled ++;
205 | [self updateAsynchronously];
206 | }
207 |
208 | - (void)setUpdatesDisabled
209 | {
210 | _updatesEnabled --;
211 | }
212 |
213 | - (void)addView:(FXBlurView *)view
214 | {
215 | if (![self.views containsObject:view])
216 | {
217 | [self.views addObject:view];
218 | [self updateAsynchronously];
219 | }
220 | }
221 |
222 | - (void)removeView:(FXBlurView *)view
223 | {
224 | NSUInteger index = [self.views indexOfObject:view];
225 | if (index != NSNotFound)
226 | {
227 | if (index <= self.viewIndex)
228 | {
229 | self.viewIndex --;
230 | }
231 | [self.views removeObjectAtIndex:index];
232 | }
233 | }
234 |
235 | - (void)updateAsynchronously
236 | {
237 | if (self.blurEnabled && !self.updating && self.updatesEnabled > 0 && [self.views count])
238 | {
239 | NSTimeInterval timeUntilNextUpdate = 1.0 / 60;
240 |
241 | //loop through until we find a view that's ready to be drawn
242 | self.viewIndex = self.viewIndex % [self.views count];
243 | for (NSUInteger i = self.viewIndex; i < [self.views count]; i++)
244 | {
245 | FXBlurView *view = self.views[i];
246 | if (view.dynamic && !view.hidden && view.window && [view shouldUpdate])
247 | {
248 | NSTimeInterval nextUpdate = [view.lastUpdate timeIntervalSinceNow] + view.updateInterval;
249 | if (!view.lastUpdate || nextUpdate <= 0)
250 | {
251 | self.updating = YES;
252 | [view updateAsynchronously:YES completion:^{
253 |
254 | //render next view
255 | self.updating = NO;
256 | self.viewIndex = i + 1;
257 | [self updateAsynchronously];
258 | }];
259 | return;
260 | }
261 | else
262 | {
263 | timeUntilNextUpdate = MIN(timeUntilNextUpdate, nextUpdate);
264 | }
265 | }
266 | }
267 |
268 | //try again, delaying until the time when the next view needs an update.
269 | self.viewIndex = 0;
270 | [self performSelector:@selector(updateAsynchronously)
271 | withObject:nil
272 | afterDelay:timeUntilNextUpdate
273 | inModes:@[NSDefaultRunLoopMode, UITrackingRunLoopMode]];
274 | }
275 | }
276 |
277 | @end
278 |
279 |
280 | @implementation FXBlurView
281 |
282 | + (void)setBlurEnabled:(BOOL)blurEnabled
283 | {
284 | [FXBlurScheduler sharedInstance].blurEnabled = blurEnabled;
285 | }
286 |
287 | + (void)setUpdatesEnabled
288 | {
289 | [[FXBlurScheduler sharedInstance] setUpdatesEnabled];
290 | }
291 |
292 | + (void)setUpdatesDisabled
293 | {
294 | [[FXBlurScheduler sharedInstance] setUpdatesDisabled];
295 | }
296 |
297 | + (Class)layerClass
298 | {
299 | return [FXBlurLayer class];
300 | }
301 |
302 | - (void)setUp
303 | {
304 | if (!_iterationsSet) _iterations = 3;
305 | if (!_blurRadiusSet) [self blurLayer].blurRadius = 40;
306 | if (!_dynamicSet) _dynamic = YES;
307 | if (!_blurEnabledSet) _blurEnabled = YES;
308 | self.updateInterval = _updateInterval;
309 | self.layer.magnificationFilter = @"linear"; // kCAFilterLinear
310 |
311 | unsigned int numberOfMethods;
312 | Method *methods = class_copyMethodList([UIView class], &numberOfMethods);
313 | for (unsigned int i = 0; i < numberOfMethods; i++)
314 | {
315 | Method method = methods[i];
316 | SEL selector = method_getName(method);
317 | if (selector == @selector(tintColor))
318 | {
319 | _tintColor = ((id (*)(id,SEL))method_getImplementation(method))(self, selector);
320 | break;
321 | }
322 | }
323 | free(methods);
324 | }
325 |
326 | - (id)initWithFrame:(CGRect)frame
327 | {
328 | if ((self = [super initWithFrame:frame]))
329 | {
330 | [self setUp];
331 | self.clipsToBounds = YES;
332 | }
333 | return self;
334 | }
335 |
336 | - (id)initWithCoder:(NSCoder *)aDecoder
337 | {
338 | if ((self = [super initWithCoder:aDecoder]))
339 | {
340 | [self setUp];
341 | }
342 | return self;
343 | }
344 |
345 | - (void)dealloc
346 | {
347 | [[NSNotificationCenter defaultCenter] removeObserver:self];
348 | }
349 |
350 | - (void)setIterations:(NSUInteger)iterations
351 | {
352 | _iterationsSet = YES;
353 | _iterations = iterations;
354 | [self setNeedsDisplay];
355 | }
356 |
357 | - (void)setBlurRadius:(CGFloat)blurRadius
358 | {
359 | _blurRadiusSet = YES;
360 | [self blurLayer].blurRadius = blurRadius;
361 | }
362 |
363 | - (CGFloat)blurRadius
364 | {
365 | return [self blurLayer].blurRadius;
366 | }
367 |
368 | - (void)setBlurEnabled:(BOOL)blurEnabled
369 | {
370 | _blurEnabledSet = YES;
371 | if (_blurEnabled != blurEnabled)
372 | {
373 | _blurEnabled = blurEnabled;
374 | [self schedule];
375 | if (_blurEnabled)
376 | {
377 | [self setNeedsDisplay];
378 | }
379 | }
380 | }
381 |
382 | - (void)setDynamic:(BOOL)dynamic
383 | {
384 | _dynamicSet = YES;
385 | if (_dynamic != dynamic)
386 | {
387 | _dynamic = dynamic;
388 | [self schedule];
389 | if (!dynamic)
390 | {
391 | [self setNeedsDisplay];
392 | }
393 | }
394 | }
395 |
396 | - (UIView *)underlyingView
397 | {
398 | return _underlyingView ?: self.superview;
399 | }
400 |
401 | - (CALayer *)underlyingLayer
402 | {
403 | return self.underlyingView.layer;
404 | }
405 |
406 | - (FXBlurLayer *)blurLayer
407 | {
408 | return (FXBlurLayer *)self.layer;
409 | }
410 |
411 | - (FXBlurLayer *)blurPresentationLayer
412 | {
413 | FXBlurLayer *blurLayer = [self blurLayer];
414 | return (FXBlurLayer *)blurLayer.presentationLayer ?: blurLayer;
415 | }
416 |
417 | - (void)setUpdateInterval:(NSTimeInterval)updateInterval
418 | {
419 | _updateInterval = updateInterval;
420 | if (_updateInterval <= 0) _updateInterval = 1.0/60;
421 | }
422 |
423 | - (void)setTintColor:(UIColor *)tintColor
424 | {
425 | _tintColor = tintColor;
426 | [self setNeedsDisplay];
427 | }
428 |
429 | - (void)didMoveToSuperview
430 | {
431 | [super didMoveToSuperview];
432 | [self.layer setNeedsDisplay];
433 | }
434 |
435 | - (void)didMoveToWindow
436 | {
437 | [super didMoveToWindow];
438 | [self schedule];
439 | }
440 |
441 | - (void)schedule
442 | {
443 | if (self.window && self.dynamic && self.blurEnabled)
444 | {
445 | [[FXBlurScheduler sharedInstance] addView:self];
446 | }
447 | else
448 | {
449 | [[FXBlurScheduler sharedInstance] removeView:self];
450 | }
451 | }
452 |
453 | - (void)setNeedsDisplay
454 | {
455 | [super setNeedsDisplay];
456 | [self.layer setNeedsDisplay];
457 | }
458 |
459 | - (BOOL)shouldUpdate
460 | {
461 | __strong CALayer *underlyingLayer = [self underlyingLayer];
462 |
463 | return
464 | underlyingLayer && !underlyingLayer.hidden &&
465 | self.blurEnabled && [FXBlurScheduler sharedInstance].blurEnabled &&
466 | !CGRectIsEmpty([self.layer.presentationLayer ?: self.layer bounds]) && !CGRectIsEmpty(underlyingLayer.bounds);
467 | }
468 |
469 | - (void)displayLayer:(__unused CALayer *)layer
470 | {
471 | [self updateAsynchronously:NO completion:NULL];
472 | }
473 |
474 | - (id)actionForLayer:(CALayer *)layer forKey:(NSString *)key
475 | {
476 | if ([key isEqualToString:@"blurRadius"])
477 | {
478 | //animations are enabled
479 | CAAnimation *action = (CAAnimation *)[super actionForLayer:layer forKey:@"backgroundColor"];
480 | if ((NSNull *)action != [NSNull null])
481 | {
482 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
483 | animation.fromValue = [layer.presentationLayer valueForKey:key];
484 |
485 | //CAMediatiming attributes
486 | animation.beginTime = action.beginTime;
487 | animation.duration = action.duration;
488 | animation.speed = action.speed;
489 | animation.timeOffset = action.timeOffset;
490 | animation.repeatCount = action.repeatCount;
491 | animation.repeatDuration = action.repeatDuration;
492 | animation.autoreverses = action.autoreverses;
493 | animation.fillMode = action.fillMode;
494 |
495 | //CAAnimation attributes
496 | animation.timingFunction = action.timingFunction;
497 | animation.delegate = action.delegate;
498 |
499 | return animation;
500 | }
501 | }
502 | return [super actionForLayer:layer forKey:key];
503 | }
504 |
505 | - (UIImage *)snapshotOfUnderlyingView
506 | {
507 | __strong FXBlurLayer *blurLayer = [self blurPresentationLayer];
508 | __strong CALayer *underlyingLayer = [self underlyingLayer];
509 | CGRect bounds = [blurLayer convertRect:blurLayer.bounds toLayer:underlyingLayer];
510 |
511 | self.lastUpdate = [NSDate date];
512 | CGFloat scale = 0.5;
513 | if (self.iterations)
514 | {
515 | CGFloat blockSize = 12.0f/self.iterations;
516 | scale = blockSize/MAX(blockSize * 2, blurLayer.blurRadius);
517 | scale = 1.0f/floorf(1.0f/scale);
518 | }
519 | CGSize size = bounds.size;
520 | if (self.contentMode == UIViewContentModeScaleToFill ||
521 | self.contentMode == UIViewContentModeScaleAspectFill ||
522 | self.contentMode == UIViewContentModeScaleAspectFit ||
523 | self.contentMode == UIViewContentModeRedraw)
524 | {
525 | //prevents edge artefacts
526 | size.width = floorf(size.width * scale) / scale;
527 | size.height = floorf(size.height * scale) / scale;
528 | }
529 | else if ([[UIDevice currentDevice].systemVersion floatValue] < 7.0f && [UIScreen mainScreen].scale == 1.0f)
530 | {
531 | //prevents pixelation on old devices
532 | scale = 1.0f;
533 | }
534 | UIGraphicsBeginImageContextWithOptions(size, NO, scale);
535 | CGContextRef context = UIGraphicsGetCurrentContext();
536 | CGContextTranslateCTM(context, -bounds.origin.x, -bounds.origin.y);
537 |
538 | NSArray *hiddenViews = [self prepareUnderlyingViewForSnapshot];
539 | [underlyingLayer renderInContext:context];
540 | [self restoreSuperviewAfterSnapshot:hiddenViews];
541 | UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
542 | UIGraphicsEndImageContext();
543 | return snapshot;
544 | }
545 |
546 | - (NSArray *)prepareUnderlyingViewForSnapshot
547 | {
548 | __strong CALayer *blurlayer = [self blurLayer];
549 | __strong CALayer *underlyingLayer = [self underlyingLayer];
550 | while (blurlayer.superlayer && blurlayer.superlayer != underlyingLayer)
551 | {
552 | blurlayer = blurlayer.superlayer;
553 | }
554 | NSMutableArray *layers = [NSMutableArray array];
555 | NSUInteger index = [underlyingLayer.sublayers indexOfObject:blurlayer];
556 | if (index != NSNotFound)
557 | {
558 | for (NSUInteger i = index; i < [underlyingLayer.sublayers count]; i++)
559 | {
560 | CALayer *layer = underlyingLayer.sublayers[i];
561 | if (!layer.hidden)
562 | {
563 | layer.hidden = YES;
564 | [layers addObject:layer];
565 | }
566 | }
567 | }
568 | return layers;
569 | }
570 |
571 | - (void)restoreSuperviewAfterSnapshot:(NSArray *)hiddenLayers
572 | {
573 | for (CALayer *layer in hiddenLayers)
574 | {
575 | layer.hidden = NO;
576 | }
577 | }
578 |
579 | - (UIImage *)blurredSnapshot:(UIImage *)snapshot radius:(CGFloat)blurRadius
580 | {
581 | return [snapshot blurredImageWithRadius:blurRadius
582 | iterations:self.iterations
583 | tintColor:self.tintColor];
584 | }
585 |
586 | - (void)setLayerContents:(UIImage *)image
587 | {
588 | self.layer.contents = (id)image.CGImage;
589 | self.layer.contentsScale = image.scale;
590 | }
591 |
592 | - (void)updateAsynchronously:(BOOL)async completion:(void (^)())completion
593 | {
594 | if ([self shouldUpdate])
595 | {
596 | UIImage *snapshot = [self snapshotOfUnderlyingView];
597 | if (async)
598 | {
599 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
600 |
601 | UIImage *blurredImage = [self blurredSnapshot:snapshot radius:self.blurRadius];
602 | dispatch_sync(dispatch_get_main_queue(), ^{
603 |
604 | [self setLayerContents:blurredImage];
605 | if (completion) completion();
606 | });
607 | });
608 | }
609 | else
610 | {
611 | [self setLayerContents:[self blurredSnapshot:snapshot radius:[self blurPresentationLayer].blurRadius]];
612 | if (completion) completion();
613 | }
614 | }
615 | else if (completion)
616 | {
617 | completion();
618 | }
619 | }
620 |
621 | @end
622 |
--------------------------------------------------------------------------------