├── _config.yml
├── OffscreenRenderDemo
├── L80.png
├── R80.png
├── L80@2x.png
├── R80@2x.png
├── RoundMask.png
├── RecRoundMask.png
├── RoundMask@2x.png
├── RecRoundMask@2x.png
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── AppDelegate.swift
├── DrawImage.swift
└── TableViewController.swift
├── OffscreenRenderDemo.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── Edmond.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
├── xcuserdata
│ ├── Edmond.xcuserdatad
│ │ └── xcschemes
│ │ │ ├── xcschememanagement.plist
│ │ │ └── OffscreenRenderDemo.xcscheme
│ └── seedante.xcuserdatad
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── OffscreenRenderDemo.xcscheme
└── project.pbxproj
└── README.md
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/OffscreenRenderDemo/L80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/L80.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo/R80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/R80.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo/L80@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/L80@2x.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo/R80@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/R80@2x.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo/RoundMask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/RoundMask.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo/RecRoundMask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/RecRoundMask.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo/RoundMask@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/RoundMask@2x.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo/RecRoundMask@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo/RecRoundMask@2x.png
--------------------------------------------------------------------------------
/OffscreenRenderDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo.xcodeproj/project.xcworkspace/xcuserdata/Edmond.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seedante/OptimizationForOffscreenRender/HEAD/OffscreenRenderDemo.xcodeproj/project.xcworkspace/xcuserdata/Edmond.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/OffscreenRenderDemo.xcodeproj/xcuserdata/Edmond.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | OffscreenRenderDemo.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | FDFAEC8E1CC79E6500A8C0C1
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo.xcodeproj/xcuserdata/seedante.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | OffscreenRenderDemo.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | FDFAEC8E1CC79E6500A8C0C1
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/OffscreenRenderDemo/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 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo/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 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // OffscreenRenderDemo
4 | //
5 | // Created by seedante on 16/4/20.
6 | // Copyright © 2016年 seedante. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // 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.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // 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.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // 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.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo.xcodeproj/xcuserdata/Edmond.xcuserdatad/xcschemes/OffscreenRenderDemo.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 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo.xcodeproj/xcuserdata/seedante.xcuserdatad/xcschemes/OffscreenRenderDemo.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## OptimizationForOffscreenRender
2 |
3 | [中文博客:离屏渲染优化](http://www.jianshu.com/p/ca51c9d3575b)
4 |
5 | ## Trigger Action
6 |
7 | Four actions can trigger offscreen render:
8 |
9 | 1. RoundedCorner
10 | 2. Shadow
11 | 3. Mask
12 | 4. GroupOpacity(almost no impact to graphics performance in my demo)
13 |
14 | Demo test all above effects and optimize the performance in a table view.
15 |
16 | 
17 |
18 | Core Graphics API don't trigger offscreen render. You should check it with Core Animation Instruments with debug option 'Color Offscreen-Renderd Yellow'.
19 |
20 | 
21 |
22 | Another proof: [Andy Matuschak's comments on this](https://lobste.rs/s/ckm4uw/a_performance-minded_take_on_ios_design/comments/itdkfh), who was a member of the UIKit team and a speaker of [WWDC 2011: Understanding UIKit Rendering](https://developer.apple.com/videos/play/wwdc2011/121/). Andy Matuschak said edge antialiasing also tirgger offscreen render, I test on iOS 8 and iOS 9, it doesn't, maybe things have change.
23 |
24 | ## Basics
25 |
26 | The relationship between UIView and CALayer, and [CALayer's visual structure](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html#//apple_ref/doc/uid/TP40004514-CH13-SW19).
27 |
28 | 
29 |
30 | 
31 |
32 |
33 | ## Trigger Condition:
34 |
35 | 1.Rounder Corener:
36 |
37 | view.layer.cornerRadius > 0
38 | view.layer.masksToBounds = true
39 |
40 | `cornerRadius`'s description:
41 |
42 | >Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to YES causes the content to be clipped to the rounded corners.
43 |
44 |
45 | So, if layer's `contents` is nil or `contents` has transparent background, you don't need to set `masksToBounds = true` to trigger offscreen render.
46 |
47 | 2.Shadow:`view.layer.shadowPath = nil`. A shadow with shadowPath won't trigger offcreen render.
48 |
49 | 3.Mask: always tirgger offscreen render.
50 |
51 | 4.GroupOpacity: `view.alpha != 1.0` and layer has nontrivial content. Note: in UITableView, only set `tableView.alpha != 1.0` can trigger offscreen render, and this has no impact to scroll performance in my demo.
52 |
53 |
54 | ## Optimization Solution:
55 |
56 |
57 | ### Avoid offscreen render if you can
58 |
59 | 1.**RounedCorner:**
60 |
61 | if layer's `contents` is nil or this `contents` has a transparent background, you just need to set `cornerRadius`. For UILabel, UITextView and UIButton, you can just set layer's `backgroundColor` and `cornerRadius` to get a rounded corner. Note: UILabel's `backgroundColor` is not its layer's `backgroundColor`.
62 |
63 | //Set backgroundColor to get corner can be see
64 | textView.backgroundColor = aColor
65 | textView.layer.cornerRadius = 5
66 | //Don't set label.backgroundColor = aColor
67 | label.layer.backgroundColor = aCGColor
68 | label.layer.cornerRadius = 5
69 |
70 | 2.**Shadow**: specify a shadow path for a shadow.
71 |
72 | ### Rasterization
73 |
74 | Rasterization works for all effects and has very good performance. This is fit for views which have static contents.
75 |
76 | view.layer.shouldRasterize = true
77 | view.layer.rasterizationScale = view.layer.contentsScale
78 | About shadow, shadowPath is better: lower GPU utilization.
79 |
80 | ### Fake effect
81 |
82 | 1.Blend with a transparent view like:
83 |
84 | 
85 |
86 | The best performance! Only problem is how get these image. Paint or draw. With a image, blend and mask get opposite effect. This solution is fit for rounded corner and mask.
87 |
88 | 2.For RoundedCorener: redraw a rouned corner image with Core Graphics API.
89 |
90 | ## Test Rasterization
91 |
92 | Test Environment:
93 |
94 | - iPad mini 1st generation with iOS 9.3.1
95 | - Xcode 7.3 with Swift 2.2
96 | - OS X 10.11.4
97 |
98 | CPU Utilization for all testes are not high: the max utilization is almost 50%.
99 |
100 | | Condition | RoundedCorner Count OnScreen | Average FPS | Average GPU Utilization | Trigger OffscreenRender |
101 | |---|---|---|---|---|
102 | |shouldRasterize = false|10|almost 44|over 80%|YES|
103 | |shouldRasterize = true |10|over 55|under 20%|YES|
104 | |shouldRasterize = false |20|almost 35|under 90%|YES|
105 | |shouldRasterize = true |20|almost 55|almost 20%|YES|
106 |
107 |
108 | (when test shouldResterize, shadowPath = nil; when shadowPath!= nil, shouldResterize = false)
109 |
110 | | Condition | Shadow Count OnScreen | Average FPS |Average GPU Utilization |Trigger OffscreenRender |
111 | |---|---|---|---|---|
112 | |shouldRasterize = false|10|almost 38|almost 73%|YES
113 | |shouldRasterize = true |10|over 55|under 30%|YES
114 | |shadowPath != nil |10|over 56|under 15%|NO
115 | |shouldRasterize = false |20|almost 22|almost 80%|YES
116 | |shouldRasterize = true |20|almost 55|under 40%|YES
117 | |shadowPath != nil |20|over 56|under 20%|NO
118 |
119 |
120 | | Condition | Mask Count OnScreen | Average FPS |Average GPU Utilization |Trigger OffscreenRender |
121 | |---|---|---|---|---|
122 | |shouldRasterize = false|10|almost 55|almost 60%|YES
123 | |shouldRasterize = true |10|over 55|almost 20%|YES
124 | |shouldRasterize = false |20|almost 37|almost 75%|YES
125 | |shouldRasterize = true |20|almost 55|under 30%|YES
126 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo/DrawImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DrawMaskImage.swift
3 | // OffscreenRenderDemo
4 | //
5 | // Created by seedante on 16/4/23.
6 | // Copyright © 2016年 seedante. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | func drawImage(image originImage: UIImage, rectSize: CGSize, roundedRadius radius: CGFloat) -> UIImage? {
12 |
13 | UIGraphicsBeginImageContextWithOptions(rectSize, false, UIScreen.main.scale)
14 | if let currentContext = UIGraphicsGetCurrentContext() {
15 | let rect = CGRect(origin: .zero, size: rectSize)
16 | currentContext.addPath(UIBezierPath(roundedRect: rect,
17 | byRoundingCorners: .allCorners,
18 | cornerRadii: CGSize(width: radius, height: radius)).cgPath)
19 | currentContext.clip()
20 |
21 | //Don't use CGContextDrawImage, coordinate system origin in UIKit and Core Graphics are vertical oppsite.
22 | originImage.draw(in: rect)
23 | currentContext.drawPath(using: .fillStroke)
24 | let roundedCornerImage = UIGraphicsGetImageFromCurrentImageContext()
25 | UIGraphicsEndImageContext()
26 | return roundedCornerImage
27 | }
28 | return nil
29 | }
30 |
31 | func UIGraphicsDrawAntiRoundedCornerImageWithRadius(_ radius: CGFloat, outerSize: CGSize, innerSize: CGSize, fillColor: UIColor) -> UIImage? {
32 |
33 | UIGraphicsBeginImageContextWithOptions(outerSize, false, UIScreen.main.scale)
34 | if let currentContext = UIGraphicsGetCurrentContext() {
35 | let xOffset = (outerSize.width - innerSize.width) / 2
36 | let yOffset = (outerSize.height - innerSize.height) / 2
37 |
38 | let hLeftUpPoint = CGPoint(x: xOffset + radius, y: yOffset)
39 | let hRightUpPoint = CGPoint(x: outerSize.width - xOffset - radius, y: yOffset)
40 | let hLeftDownPoint = CGPoint(x: xOffset + radius, y: outerSize.height - yOffset)
41 |
42 | let vLeftUpPoint = CGPoint(x: xOffset, y: yOffset + radius)
43 | let vRightDownPoint = CGPoint(x: outerSize.width - xOffset, y: outerSize.height - yOffset - radius)
44 |
45 | let centerLeftUp = CGPoint(x: xOffset + radius, y: yOffset + radius)
46 | let centerRightUp = CGPoint(x: outerSize.width - xOffset - radius, y: yOffset + radius)
47 | let centerLeftDown = CGPoint(x: xOffset + radius, y: outerSize.height - yOffset - radius)
48 | let centerRightDown = CGPoint(x: outerSize.width - xOffset - radius, y: outerSize.height - yOffset - radius)
49 | let bezierPath = UIBezierPath()
50 |
51 | bezierPath.move(to: hLeftUpPoint)
52 | bezierPath.addLine(to: hRightUpPoint)
53 | bezierPath.addArc(withCenter: centerRightUp, radius: radius, startAngle: CGFloat(M_PI * 3 / 2), endAngle: CGFloat(M_PI * 2), clockwise: true)
54 | bezierPath.addLine(to: vRightDownPoint)
55 | bezierPath.addArc(withCenter: centerRightDown, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI / 2), clockwise: true)
56 | bezierPath.addLine(to: hLeftDownPoint)
57 | bezierPath.addArc(withCenter: centerLeftDown, radius: radius, startAngle: CGFloat(M_PI / 2), endAngle: CGFloat(M_PI), clockwise: true)
58 | bezierPath.addLine(to: vLeftUpPoint)
59 | bezierPath.addArc(withCenter: centerLeftUp, radius: radius, startAngle: CGFloat(M_PI), endAngle: CGFloat(M_PI * 3 / 2), clockwise: true)
60 | bezierPath.addLine(to: hLeftUpPoint)
61 | bezierPath.close()
62 |
63 | //There is a strange bug: if draw drection of outer path is same with inner path, final result is just outer path.
64 | bezierPath.move(to: CGPoint.zero)
65 | bezierPath.addLine(to: CGPoint(x: 0, y: outerSize.height))
66 | bezierPath.addLine(to: CGPoint(x: outerSize.width, y: outerSize.height))
67 | bezierPath.addLine(to: CGPoint(x: outerSize.width, y: 0))
68 | bezierPath.addLine(to: CGPoint.zero)
69 | bezierPath.close()
70 |
71 | fillColor.setFill()
72 | bezierPath.fill()
73 |
74 | currentContext.drawPath(using: .fillStroke)
75 | let antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext()
76 | UIGraphicsEndImageContext()
77 | return antiRoundedCornerImage
78 | }
79 | return nil
80 | }
81 |
82 | func UIGraphicsDrawAntiRoundedCornerImageWithRadius(_ radius: CGFloat, rectSize: CGSize, fillColor: UIColor) -> UIImage? {
83 | UIGraphicsBeginImageContextWithOptions(rectSize, false, UIScreen.main.scale)
84 | if let currentContext = UIGraphicsGetCurrentContext() {
85 |
86 | let bezierPath = UIBezierPath()
87 |
88 | let hLeftUpPoint = CGPoint(x: radius, y: 0)
89 | let hRightUpPoint = CGPoint(x: rectSize.width - radius, y: 0)
90 | let hLeftDownPoint = CGPoint(x: radius, y: rectSize.height)
91 |
92 | let vLeftUpPoint = CGPoint(x: 0, y: radius)
93 | let vRightDownPoint = CGPoint(x: rectSize.width, y: rectSize.height - radius)
94 |
95 | let centerLeftUp = CGPoint(x: radius, y: radius)
96 | let centerRightUp = CGPoint(x: rectSize.width - radius, y: radius)
97 | let centerLeftDown = CGPoint(x: radius, y: rectSize.height - radius)
98 | let centerRightDown = CGPoint(x: rectSize.width - radius, y: rectSize.height - radius)
99 |
100 | bezierPath.move(to: hLeftUpPoint)
101 | bezierPath.addLine(to: hRightUpPoint)
102 | bezierPath.addArc(withCenter: centerRightUp, radius: radius, startAngle: CGFloat(M_PI * 3 / 2), endAngle: CGFloat(M_PI * 2), clockwise: true)
103 | bezierPath.addLine(to: vRightDownPoint)
104 | bezierPath.addArc(withCenter: centerRightDown, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI / 2), clockwise: true)
105 | bezierPath.addLine(to: hLeftDownPoint)
106 | bezierPath.addArc(withCenter: centerLeftDown, radius: radius, startAngle: CGFloat(M_PI / 2), endAngle: CGFloat(M_PI), clockwise: true)
107 | bezierPath.addLine(to: vLeftUpPoint)
108 | bezierPath.addArc(withCenter: centerLeftUp, radius: radius, startAngle: CGFloat(M_PI), endAngle: CGFloat(M_PI * 3 / 2), clockwise: true)
109 | bezierPath.addLine(to: hLeftUpPoint)
110 | bezierPath.close()
111 |
112 | //If draw drection of outer path is same with inner path, final result is just outer path.
113 | bezierPath.move(to: CGPoint.zero)
114 | bezierPath.addLine(to: CGPoint(x: 0, y: rectSize.height))
115 | bezierPath.addLine(to: CGPoint(x: rectSize.width, y: rectSize.height))
116 | bezierPath.addLine(to: CGPoint(x: rectSize.width, y: 0))
117 | bezierPath.addLine(to: CGPoint.zero)
118 | bezierPath.close()
119 |
120 | fillColor.setFill()
121 | bezierPath.fill()
122 |
123 | currentContext.drawPath(using: .fillStroke)
124 | let antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext()
125 | UIGraphicsEndImageContext()
126 |
127 | return antiRoundedCornerImage
128 | }
129 | return nil
130 | }
131 |
132 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo/TableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewController.swift
3 | // OffscreenRenderDemo
4 | //
5 | // Created by seedante on 16/4/20.
6 | // Copyright © 2016年 seedante. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 |
12 | class TableViewController: UITableViewController {
13 |
14 | let cellIdentifier = "Cell"
15 | let avatorImageL = UIImage(named: "L80.png")
16 | let avatorImageR = UIImage(named: "R80.png")
17 | let blendImage = UIImage(named: "RecRoundMask.png")
18 | let maskImage = UIImage(named: "RoundMask.png")
19 | // let maskImageNoEffect = UIImage(named: "L80.png")
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | /*GroupOpacity Test: No obvious impact to performance almost in this demo.*/
25 | // enableGroupOpacityOn(view)
26 |
27 | }
28 |
29 | override func didReceiveMemoryWarning() {
30 | super.didReceiveMemoryWarning()
31 | // Dispose of any resources that can be recreated.
32 | }
33 |
34 | // MARK: - Table view data source
35 |
36 |
37 | override func numberOfSections(in tableView: UITableView) -> Int {
38 | return 1
39 | }
40 |
41 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
42 | return 100
43 | }
44 |
45 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
46 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
47 |
48 | let labelL = cell.viewWithTag(30) as! UILabel
49 | let labelR = cell.viewWithTag(40) as! UILabel
50 | labelL.text = "OffscreenRender" + String(indexPath.row)
51 | labelR.text = String(indexPath.row) + "离屏渲染"
52 |
53 | //I test on iPad mini 1st generation, iOS 9.3.1, latter iOS devices maybe have better performance.
54 |
55 | //No effect Test
56 | displayCell(cell)
57 |
58 | /*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/
59 | /*RounderCorner Test*/
60 |
61 | //System Rounded Corner: if layer's contents is not nil, masksToBounds must be true. Is cornerRadius bigger, performance worse? No, when cornerRadius > 0, performance is same almost.
62 | // applySystemRoundedCornerOn(cell)
63 |
64 | /*RounedCorner solution:
65 | 1. Redraw contents and clip as rouned corner contens;
66 | 2. Blend with a view which has transparent part contents, like a mask. The best performance! Here I use a UIImageView with part transparent contents.
67 | */
68 |
69 | //Redraw in main thread, put it in background thread is better.
70 | // redrawRounedCornerInMainThreadOn(cell)
71 |
72 | //Redraw in background thread, performance is nice.
73 | // redrawRoundedCornerInBackgroundThreadOn(cell)
74 |
75 | //This solution needs a image which is partly transparent. You can paint it by Sketch, PaintCode, or draw it with Core Graphics API.
76 | // blendRoundedCornerOn(cell)
77 |
78 | /*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/
79 | /*Shadow Test: shadow is not compatible with system rouned corner, because layer.masksToBounds can't be true in shadow affect.*/
80 | // dropShadownOn(cell)
81 |
82 | //Optimization for shadow: a shadow path can cancel offscreen render effect
83 | // let avatorViewL = cell.viewWithTag(10) as! UIImageView
84 | // specifyShadowPathOn(avatorViewL)
85 | // let avatorViewR = cell.viewWithTag(20) as! UIImageView
86 | // specifyShadowPathOn(avatorViewR)
87 |
88 | /*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/
89 | /*Mask Test: Is maskLayer more transparent part, performance better? No obvious impact.*/
90 | // applyMaskOn(cell)
91 |
92 | /*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/
93 | //Ultimate solution: Rasterization, works for roundedCorner, shadow, mask and has very good performance.
94 | // enableRasterizationOn(cell)
95 |
96 | //Simulate danamic content
97 | // dynamicallyUpdateCell(cell)
98 | return cell
99 | }
100 |
101 | func dynamicallyUpdateCell(_ cell: UITableViewCell){
102 |
103 | let number = Int(UInt32(arc4random()) % UInt32(10))
104 |
105 | let labelL = cell.viewWithTag(30) as! UILabel
106 | labelL.text = "OffscreenRender" + String(number)
107 |
108 | let labelR = cell.viewWithTag(40) as! UILabel
109 | labelR.text = String(number) + "离屏渲染"
110 |
111 |
112 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
113 | avatorViewL.layer.cornerRadius = CGFloat(number)
114 | avatorViewL.clipsToBounds = true
115 |
116 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
117 | avatorViewR.layer.cornerRadius = CGFloat(number)
118 | avatorViewR.clipsToBounds = true
119 |
120 | let delay = TimeInterval(number) * 0.1
121 | perform(#selector(TableViewController.dynamicallyUpdateCell(_:)), with: cell, afterDelay: delay)
122 | }
123 |
124 | func displayCell(_ cell: UITableViewCell) {
125 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
126 | avatorViewL.image = avatorImageL
127 |
128 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
129 | avatorViewR.image = avatorImageR
130 | }
131 |
132 | func applySystemRoundedCornerOn(_ cell: UITableViewCell) {
133 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
134 | avatorViewL.image = avatorImageL
135 | avatorViewL.layer.cornerRadius = 10
136 | avatorViewL.layer.masksToBounds = true
137 |
138 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
139 | avatorViewR.image = avatorImageR
140 | avatorViewR.layer.cornerRadius = 10
141 | avatorViewR.layer.masksToBounds = true
142 | }
143 |
144 | func redrawRounedCornerInMainThreadOn(_ cell: UITableViewCell) {
145 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
146 | let roundedCornerImageL = drawImage(image: avatorImageL!, rectSize: CGSize(width: 80, height: 80), roundedRadius: 10.0)
147 | avatorViewL.image = roundedCornerImageL
148 |
149 |
150 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
151 | let roundedCornerImageR = drawImage(image: avatorImageR!, rectSize: CGSize(width: 80, height: 80), roundedRadius: 10.0)
152 | avatorViewR.image = roundedCornerImageR
153 |
154 | }
155 |
156 | func redrawRoundedCornerInBackgroundThreadOn(_ cell: UITableViewCell) {
157 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
158 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
159 |
160 | DispatchQueue.global(qos: .default).async {
161 | let roundedCornerImageL = drawImage(image: self.avatorImageL!, rectSize: CGSize(width: 80, height: 80), roundedRadius: 10.0)
162 | let roundedCornerImageR = drawImage(image: self.avatorImageR!, rectSize: CGSize(width: 80, height: 80), roundedRadius: 10.0)
163 | DispatchQueue.main.async {
164 | avatorViewL.image = roundedCornerImageL
165 | avatorViewR.image = roundedCornerImageR
166 | }
167 | }
168 | }
169 |
170 | func blendRoundedCornerOn(_ cell: UITableViewCell) {
171 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
172 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
173 | avatorViewL.image = avatorImageL
174 | avatorViewR.image = avatorImageR
175 |
176 | let blendViewL = cell.viewWithTag(50) as! UIImageView
177 | blendViewL.image = blendImage
178 | blendViewL.isHidden = false
179 |
180 | let blendViewR = cell.viewWithTag(60) as! UIImageView
181 | blendViewR.image = blendImage
182 | blendViewR.isHidden = false
183 | }
184 |
185 | func dropShadownOn(_ cell: UITableViewCell){
186 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
187 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
188 | avatorViewL.image = avatorImageL
189 | avatorViewR.image = avatorImageR
190 |
191 | avatorViewL.layer.shadowColor = UIColor.red.cgColor
192 | avatorViewL.layer.shadowOffset = CGSize(width: 5, height: 5)
193 | avatorViewL.layer.shadowOpacity = 1
194 |
195 | avatorViewR.layer.shadowColor = UIColor.red.cgColor
196 | avatorViewR.layer.shadowOffset = CGSize(width: 5, height: 5)
197 | avatorViewR.layer.shadowOpacity = 1
198 | }
199 |
200 | //Optimization for shadow
201 | func specifyShadowPathOn(_ view: UIView) {
202 | let path = UIBezierPath(rect: view.bounds)
203 | view.layer.shadowPath = path.cgPath
204 | }
205 |
206 |
207 | func applyMaskOn(_ cell: UITableViewCell) {
208 | let avatorViewL = cell.viewWithTag(10) as! UIImageView
209 | let avatorViewR = cell.viewWithTag(20) as! UIImageView
210 | avatorViewL.image = avatorImageL
211 | avatorViewR.image = avatorImageR
212 |
213 | if #available(iOS 8.0, *) {
214 | avatorViewL.mask = UIImageView(image: maskImage)
215 | avatorViewR.mask = UIImageView(image: maskImage)
216 | } else {
217 | let maskLayer1 = CALayer()
218 | maskLayer1.frame = avatorViewL.bounds
219 | maskLayer1.contents = maskImage?.cgImage
220 | avatorViewL.layer.mask = maskLayer1
221 |
222 | let maskLayer2 = CALayer()
223 | maskLayer2.frame = avatorViewR.bounds
224 | maskLayer2.contents = maskImage?.cgImage
225 | avatorViewR.layer.mask = maskLayer2
226 | }
227 |
228 | //Or use CAShapeLayer
229 | // let roundedRectPath = UIBezierPath(roundedRect: avatorViewL.bounds, byRoundingCorners: .AllCorners, cornerRadii: CGSize(width: 10, height: 10))
230 | // let shapeLayerL = CAShapeLayer()
231 | // shapeLayerL.path = roundedRectPath.CGPath
232 | // avatorViewL.layer.mask = shapeLayerL
233 | //
234 | // let shapeLayerR = CAShapeLayer()
235 | // shapeLayerR.path = roundedRectPath.CGPath
236 | // avatorViewR.layer.mask = shapeLayerR
237 | }
238 |
239 | func enableGroupOpacityOn(_ view: UIView) {
240 | /*
241 | Group Opacity Test:
242 |
243 | -------------------------------------------------------------------------------------------------------------------------------------------------------------
244 | var allowsGroupOpacity: Bool
245 |
246 | Discussion:
247 |
248 | When the value is YES and the layer’s opacity property value is less than 1.0, the layer is allowed to composite itself as a group separate from its parent.
249 | This gives correct results when the layer contains multiple opaque components, but may reduce performance.
250 |
251 | The default value is read from the boolean UIViewGroupOpacity property in the main bundle’s Info.plist file.
252 | If no value is found, the default value is YES for apps linked against the iOS 7 SDK or later and NO for apps linked against an earlier SDK.
253 | -------------------------------------------------------------------------------------------------------------------------------------------------------------
254 | In WWDC 2014 419: Advanced Graphics and Animation Performance, performance consideration:
255 |
256 | Will introduce offscreen passes:
257 | If layer is not opaque (opacity != 1.0)
258 | And if layer has nontrivial content (child layers or background image)
259 | -->Sub view hierarchy needs to be composited before being blended
260 | Always turn it off if not needed.
261 |
262 | layer's opacity = view's alpha
263 | -------------------------------------------------------------------------------------------------------------------------------------------------------------
264 | So, `allowsGroupOpacity` is `true` in this project with default configuration.
265 | But in UITableView, 'cell.alpha != 1.0' can't trigger offscreen render and can't change alpha actualy, cell.contentView.alpha do.
266 | How trigger offscreen render on cell? Set 'tableView.alpha != 1.0', you can check it with `Color Offscreen-Rendered Yellow` in Core Animation Instruments.
267 | But(again), no impact to scroll performance.
268 | You can easily get offscreen render on general view which has subview by change its alpha < 1.
269 | -------------------------------------------------------------------------------------------------------------------------------------------------------------
270 | */
271 | view.alpha = 0.9
272 | }
273 |
274 | /*
275 | //Ultimate Solution: Rasterization
276 |
277 | Typical use cases:
278 | Avoid redrawing expensive effects for static content
279 | Avoid redrawing of complex view hierarchies
280 | */
281 | func enableRasterizationOn(_ view: UIView) {
282 | view.layer.shouldRasterize = true
283 | view.layer.rasterizationScale = view.layer.contentsScale
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo/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 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
52 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
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 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/OffscreenRenderDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | FD0551841CC7BA3F00AAFF82 /* L80.png in Resources */ = {isa = PBXBuildFile; fileRef = FD05517E1CC7BA3F00AAFF82 /* L80.png */; };
11 | FD0551871CC7BA3F00AAFF82 /* R80.png in Resources */ = {isa = PBXBuildFile; fileRef = FD0551811CC7BA3F00AAFF82 /* R80.png */; };
12 | FD0551911CC7C5D900AAFF82 /* L80@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FD05518F1CC7C5D900AAFF82 /* L80@2x.png */; };
13 | FD0551921CC7C5D900AAFF82 /* R80@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FD0551901CC7C5D900AAFF82 /* R80@2x.png */; };
14 | FD0551951CC885CA00AAFF82 /* RecRoundMask.png in Resources */ = {isa = PBXBuildFile; fileRef = FD0551931CC885CA00AAFF82 /* RecRoundMask.png */; };
15 | FD0551961CC885CA00AAFF82 /* RecRoundMask@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FD0551941CC885CA00AAFF82 /* RecRoundMask@2x.png */; };
16 | FD2EA3621CCBD075003AA2D8 /* DrawImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2EA3611CCBD075003AA2D8 /* DrawImage.swift */; };
17 | FD757CC91CCF5F8C00496364 /* RoundMask.png in Resources */ = {isa = PBXBuildFile; fileRef = FD757CC71CCF5F8C00496364 /* RoundMask.png */; };
18 | FD757CCA1CCF5F8C00496364 /* RoundMask@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FD757CC81CCF5F8C00496364 /* RoundMask@2x.png */; };
19 | FD757CCC1CCF8C6600496364 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = FD757CCB1CCF8C6600496364 /* README.md */; };
20 | FDFAEC931CC79E6500A8C0C1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFAEC921CC79E6500A8C0C1 /* AppDelegate.swift */; };
21 | FDFAEC981CC79E6500A8C0C1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FDFAEC961CC79E6500A8C0C1 /* Main.storyboard */; };
22 | FDFAEC9A1CC79E6500A8C0C1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FDFAEC991CC79E6500A8C0C1 /* Assets.xcassets */; };
23 | FDFAEC9D1CC79E6500A8C0C1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FDFAEC9B1CC79E6500A8C0C1 /* LaunchScreen.storyboard */; };
24 | FDFAECA51CC7A1DF00A8C0C1 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFAECA41CC7A1DF00A8C0C1 /* TableViewController.swift */; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXFileReference section */
28 | FD05517E1CC7BA3F00AAFF82 /* L80.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = L80.png; sourceTree = ""; };
29 | FD0551811CC7BA3F00AAFF82 /* R80.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = R80.png; sourceTree = ""; };
30 | FD05518F1CC7C5D900AAFF82 /* L80@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "L80@2x.png"; sourceTree = ""; };
31 | FD0551901CC7C5D900AAFF82 /* R80@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "R80@2x.png"; sourceTree = ""; };
32 | FD0551931CC885CA00AAFF82 /* RecRoundMask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = RecRoundMask.png; sourceTree = ""; };
33 | FD0551941CC885CA00AAFF82 /* RecRoundMask@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "RecRoundMask@2x.png"; sourceTree = ""; };
34 | FD2EA3611CCBD075003AA2D8 /* DrawImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawImage.swift; sourceTree = ""; };
35 | FD757CC71CCF5F8C00496364 /* RoundMask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = RoundMask.png; sourceTree = ""; };
36 | FD757CC81CCF5F8C00496364 /* RoundMask@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "RoundMask@2x.png"; sourceTree = ""; };
37 | FD757CCB1CCF8C6600496364 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
38 | FDFAEC8F1CC79E6500A8C0C1 /* OffscreenRenderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OffscreenRenderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
39 | FDFAEC921CC79E6500A8C0C1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
40 | FDFAEC971CC79E6500A8C0C1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
41 | FDFAEC991CC79E6500A8C0C1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
42 | FDFAEC9C1CC79E6500A8C0C1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
43 | FDFAEC9E1CC79E6500A8C0C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
44 | FDFAECA41CC7A1DF00A8C0C1 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | FDFAEC8C1CC79E6500A8C0C1 /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | FD05518A1CC7BA4600AAFF82 /* Image */ = {
59 | isa = PBXGroup;
60 | children = (
61 | FD05517E1CC7BA3F00AAFF82 /* L80.png */,
62 | FD05518F1CC7C5D900AAFF82 /* L80@2x.png */,
63 | FD0551811CC7BA3F00AAFF82 /* R80.png */,
64 | FD0551901CC7C5D900AAFF82 /* R80@2x.png */,
65 | FD0551931CC885CA00AAFF82 /* RecRoundMask.png */,
66 | FD0551941CC885CA00AAFF82 /* RecRoundMask@2x.png */,
67 | FD757CC71CCF5F8C00496364 /* RoundMask.png */,
68 | FD757CC81CCF5F8C00496364 /* RoundMask@2x.png */,
69 | );
70 | name = Image;
71 | sourceTree = "";
72 | };
73 | FDFAEC861CC79E6400A8C0C1 = {
74 | isa = PBXGroup;
75 | children = (
76 | FD757CCB1CCF8C6600496364 /* README.md */,
77 | FDFAEC911CC79E6500A8C0C1 /* OffscreenRenderDemo */,
78 | FDFAEC901CC79E6500A8C0C1 /* Products */,
79 | );
80 | sourceTree = "";
81 | };
82 | FDFAEC901CC79E6500A8C0C1 /* Products */ = {
83 | isa = PBXGroup;
84 | children = (
85 | FDFAEC8F1CC79E6500A8C0C1 /* OffscreenRenderDemo.app */,
86 | );
87 | name = Products;
88 | sourceTree = "";
89 | };
90 | FDFAEC911CC79E6500A8C0C1 /* OffscreenRenderDemo */ = {
91 | isa = PBXGroup;
92 | children = (
93 | FD05518A1CC7BA4600AAFF82 /* Image */,
94 | FDFAEC921CC79E6500A8C0C1 /* AppDelegate.swift */,
95 | FDFAECA41CC7A1DF00A8C0C1 /* TableViewController.swift */,
96 | FD2EA3611CCBD075003AA2D8 /* DrawImage.swift */,
97 | FDFAEC961CC79E6500A8C0C1 /* Main.storyboard */,
98 | FDFAEC991CC79E6500A8C0C1 /* Assets.xcassets */,
99 | FDFAEC9B1CC79E6500A8C0C1 /* LaunchScreen.storyboard */,
100 | FDFAEC9E1CC79E6500A8C0C1 /* Info.plist */,
101 | );
102 | path = OffscreenRenderDemo;
103 | sourceTree = "";
104 | };
105 | /* End PBXGroup section */
106 |
107 | /* Begin PBXNativeTarget section */
108 | FDFAEC8E1CC79E6500A8C0C1 /* OffscreenRenderDemo */ = {
109 | isa = PBXNativeTarget;
110 | buildConfigurationList = FDFAECA11CC79E6500A8C0C1 /* Build configuration list for PBXNativeTarget "OffscreenRenderDemo" */;
111 | buildPhases = (
112 | FDFAEC8B1CC79E6500A8C0C1 /* Sources */,
113 | FDFAEC8C1CC79E6500A8C0C1 /* Frameworks */,
114 | FDFAEC8D1CC79E6500A8C0C1 /* Resources */,
115 | );
116 | buildRules = (
117 | );
118 | dependencies = (
119 | );
120 | name = OffscreenRenderDemo;
121 | productName = OffscreenRenderDemo;
122 | productReference = FDFAEC8F1CC79E6500A8C0C1 /* OffscreenRenderDemo.app */;
123 | productType = "com.apple.product-type.application";
124 | };
125 | /* End PBXNativeTarget section */
126 |
127 | /* Begin PBXProject section */
128 | FDFAEC871CC79E6400A8C0C1 /* Project object */ = {
129 | isa = PBXProject;
130 | attributes = {
131 | LastSwiftUpdateCheck = 0730;
132 | LastUpgradeCheck = 0820;
133 | ORGANIZATIONNAME = seedante;
134 | TargetAttributes = {
135 | FDFAEC8E1CC79E6500A8C0C1 = {
136 | CreatedOnToolsVersion = 7.3;
137 | LastSwiftMigration = 0820;
138 | };
139 | };
140 | };
141 | buildConfigurationList = FDFAEC8A1CC79E6400A8C0C1 /* Build configuration list for PBXProject "OffscreenRenderDemo" */;
142 | compatibilityVersion = "Xcode 3.2";
143 | developmentRegion = English;
144 | hasScannedForEncodings = 0;
145 | knownRegions = (
146 | en,
147 | Base,
148 | );
149 | mainGroup = FDFAEC861CC79E6400A8C0C1;
150 | productRefGroup = FDFAEC901CC79E6500A8C0C1 /* Products */;
151 | projectDirPath = "";
152 | projectRoot = "";
153 | targets = (
154 | FDFAEC8E1CC79E6500A8C0C1 /* OffscreenRenderDemo */,
155 | );
156 | };
157 | /* End PBXProject section */
158 |
159 | /* Begin PBXResourcesBuildPhase section */
160 | FDFAEC8D1CC79E6500A8C0C1 /* Resources */ = {
161 | isa = PBXResourcesBuildPhase;
162 | buildActionMask = 2147483647;
163 | files = (
164 | FD0551841CC7BA3F00AAFF82 /* L80.png in Resources */,
165 | FDFAEC9D1CC79E6500A8C0C1 /* LaunchScreen.storyboard in Resources */,
166 | FD0551961CC885CA00AAFF82 /* RecRoundMask@2x.png in Resources */,
167 | FD0551911CC7C5D900AAFF82 /* L80@2x.png in Resources */,
168 | FD757CC91CCF5F8C00496364 /* RoundMask.png in Resources */,
169 | FD0551951CC885CA00AAFF82 /* RecRoundMask.png in Resources */,
170 | FD0551921CC7C5D900AAFF82 /* R80@2x.png in Resources */,
171 | FD757CCA1CCF5F8C00496364 /* RoundMask@2x.png in Resources */,
172 | FDFAEC9A1CC79E6500A8C0C1 /* Assets.xcassets in Resources */,
173 | FDFAEC981CC79E6500A8C0C1 /* Main.storyboard in Resources */,
174 | FD0551871CC7BA3F00AAFF82 /* R80.png in Resources */,
175 | );
176 | runOnlyForDeploymentPostprocessing = 0;
177 | };
178 | /* End PBXResourcesBuildPhase section */
179 |
180 | /* Begin PBXSourcesBuildPhase section */
181 | FDFAEC8B1CC79E6500A8C0C1 /* Sources */ = {
182 | isa = PBXSourcesBuildPhase;
183 | buildActionMask = 2147483647;
184 | files = (
185 | FDFAEC931CC79E6500A8C0C1 /* AppDelegate.swift in Sources */,
186 | FDFAECA51CC7A1DF00A8C0C1 /* TableViewController.swift in Sources */,
187 | FD757CCC1CCF8C6600496364 /* README.md in Sources */,
188 | FD2EA3621CCBD075003AA2D8 /* DrawImage.swift in Sources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXSourcesBuildPhase section */
193 |
194 | /* Begin PBXVariantGroup section */
195 | FDFAEC961CC79E6500A8C0C1 /* Main.storyboard */ = {
196 | isa = PBXVariantGroup;
197 | children = (
198 | FDFAEC971CC79E6500A8C0C1 /* Base */,
199 | );
200 | name = Main.storyboard;
201 | sourceTree = "";
202 | };
203 | FDFAEC9B1CC79E6500A8C0C1 /* LaunchScreen.storyboard */ = {
204 | isa = PBXVariantGroup;
205 | children = (
206 | FDFAEC9C1CC79E6500A8C0C1 /* Base */,
207 | );
208 | name = LaunchScreen.storyboard;
209 | sourceTree = "";
210 | };
211 | /* End PBXVariantGroup section */
212 |
213 | /* Begin XCBuildConfiguration section */
214 | FDFAEC9F1CC79E6500A8C0C1 /* Debug */ = {
215 | isa = XCBuildConfiguration;
216 | buildSettings = {
217 | ALWAYS_SEARCH_USER_PATHS = NO;
218 | CLANG_ANALYZER_NONNULL = YES;
219 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
220 | CLANG_CXX_LIBRARY = "libc++";
221 | CLANG_ENABLE_MODULES = YES;
222 | CLANG_ENABLE_OBJC_ARC = YES;
223 | CLANG_WARN_BOOL_CONVERSION = YES;
224 | CLANG_WARN_CONSTANT_CONVERSION = YES;
225 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
226 | CLANG_WARN_EMPTY_BODY = YES;
227 | CLANG_WARN_ENUM_CONVERSION = YES;
228 | CLANG_WARN_INFINITE_RECURSION = YES;
229 | CLANG_WARN_INT_CONVERSION = YES;
230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
231 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
232 | CLANG_WARN_UNREACHABLE_CODE = YES;
233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
234 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
235 | COPY_PHASE_STRIP = NO;
236 | DEBUG_INFORMATION_FORMAT = dwarf;
237 | ENABLE_STRICT_OBJC_MSGSEND = YES;
238 | ENABLE_TESTABILITY = YES;
239 | GCC_C_LANGUAGE_STANDARD = gnu99;
240 | GCC_DYNAMIC_NO_PIC = NO;
241 | GCC_NO_COMMON_BLOCKS = YES;
242 | GCC_OPTIMIZATION_LEVEL = 0;
243 | GCC_PREPROCESSOR_DEFINITIONS = (
244 | "DEBUG=1",
245 | "$(inherited)",
246 | );
247 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
248 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
249 | GCC_WARN_UNDECLARED_SELECTOR = YES;
250 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
251 | GCC_WARN_UNUSED_FUNCTION = YES;
252 | GCC_WARN_UNUSED_VARIABLE = YES;
253 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
254 | MTL_ENABLE_DEBUG_INFO = YES;
255 | ONLY_ACTIVE_ARCH = YES;
256 | SDKROOT = iphoneos;
257 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
258 | TARGETED_DEVICE_FAMILY = "1,2";
259 | };
260 | name = Debug;
261 | };
262 | FDFAECA01CC79E6500A8C0C1 /* Release */ = {
263 | isa = XCBuildConfiguration;
264 | buildSettings = {
265 | ALWAYS_SEARCH_USER_PATHS = NO;
266 | CLANG_ANALYZER_NONNULL = YES;
267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
268 | CLANG_CXX_LIBRARY = "libc++";
269 | CLANG_ENABLE_MODULES = YES;
270 | CLANG_ENABLE_OBJC_ARC = YES;
271 | CLANG_WARN_BOOL_CONVERSION = YES;
272 | CLANG_WARN_CONSTANT_CONVERSION = YES;
273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
274 | CLANG_WARN_EMPTY_BODY = YES;
275 | CLANG_WARN_ENUM_CONVERSION = YES;
276 | CLANG_WARN_INFINITE_RECURSION = YES;
277 | CLANG_WARN_INT_CONVERSION = YES;
278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
279 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
280 | CLANG_WARN_UNREACHABLE_CODE = YES;
281 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
282 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
283 | COPY_PHASE_STRIP = NO;
284 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
285 | ENABLE_NS_ASSERTIONS = NO;
286 | ENABLE_STRICT_OBJC_MSGSEND = YES;
287 | GCC_C_LANGUAGE_STANDARD = gnu99;
288 | GCC_NO_COMMON_BLOCKS = YES;
289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
291 | GCC_WARN_UNDECLARED_SELECTOR = YES;
292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
293 | GCC_WARN_UNUSED_FUNCTION = YES;
294 | GCC_WARN_UNUSED_VARIABLE = YES;
295 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
296 | MTL_ENABLE_DEBUG_INFO = NO;
297 | SDKROOT = iphoneos;
298 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
299 | TARGETED_DEVICE_FAMILY = "1,2";
300 | VALIDATE_PRODUCT = YES;
301 | };
302 | name = Release;
303 | };
304 | FDFAECA21CC79E6500A8C0C1 /* Debug */ = {
305 | isa = XCBuildConfiguration;
306 | buildSettings = {
307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
308 | DEVELOPMENT_TEAM = "";
309 | INFOPLIST_FILE = OffscreenRenderDemo/Info.plist;
310 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
311 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
312 | PRODUCT_BUNDLE_IDENTIFIER = seedante.OffscreenRenderDemo;
313 | PRODUCT_NAME = "$(TARGET_NAME)";
314 | SWIFT_VERSION = 3.0;
315 | };
316 | name = Debug;
317 | };
318 | FDFAECA31CC79E6500A8C0C1 /* Release */ = {
319 | isa = XCBuildConfiguration;
320 | buildSettings = {
321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
322 | DEVELOPMENT_TEAM = "";
323 | INFOPLIST_FILE = OffscreenRenderDemo/Info.plist;
324 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
326 | PRODUCT_BUNDLE_IDENTIFIER = seedante.OffscreenRenderDemo;
327 | PRODUCT_NAME = "$(TARGET_NAME)";
328 | SWIFT_VERSION = 3.0;
329 | };
330 | name = Release;
331 | };
332 | /* End XCBuildConfiguration section */
333 |
334 | /* Begin XCConfigurationList section */
335 | FDFAEC8A1CC79E6400A8C0C1 /* Build configuration list for PBXProject "OffscreenRenderDemo" */ = {
336 | isa = XCConfigurationList;
337 | buildConfigurations = (
338 | FDFAEC9F1CC79E6500A8C0C1 /* Debug */,
339 | FDFAECA01CC79E6500A8C0C1 /* Release */,
340 | );
341 | defaultConfigurationIsVisible = 0;
342 | defaultConfigurationName = Release;
343 | };
344 | FDFAECA11CC79E6500A8C0C1 /* Build configuration list for PBXNativeTarget "OffscreenRenderDemo" */ = {
345 | isa = XCConfigurationList;
346 | buildConfigurations = (
347 | FDFAECA21CC79E6500A8C0C1 /* Debug */,
348 | FDFAECA31CC79E6500A8C0C1 /* Release */,
349 | );
350 | defaultConfigurationIsVisible = 0;
351 | defaultConfigurationName = Release;
352 | };
353 | /* End XCConfigurationList section */
354 | };
355 | rootObject = FDFAEC871CC79E6400A8C0C1 /* Project object */;
356 | }
357 |
--------------------------------------------------------------------------------