├── _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 | ![](http://upload-images.jianshu.io/upload_images/37334-db123028f84273b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 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 | ![Core Animation Instruments Debug Options](http://upload-images.jianshu.io/upload_images/37334-909659db842314aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 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 | ![Relationship of UIView and CALayer](https://github.com/seedante/iOS-Note/blob/master/GraphicsPerformance/Relationship%20of%20UIView%20and%20CALayer.png?raw=true) 29 | 30 | ![UIImageView Structure](https://github.com/seedante/iOS-Note/blob/master/GraphicsPerformance/UIImageView%20Structure.png?raw=true) 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 | ![Transparent Overlay](https://github.com/seedante/iOS-Note/blob/master/GraphicsPerformance/Transparent%20Overlay.png?raw=true) 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 | 55 | 61 | 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 | --------------------------------------------------------------------------------