Skip to content

Commit 8b6e7a5

Browse files
committed
Adds an object that captures the screen using ReplayKit.
1 parent 3efab70 commit 8b6e7a5

File tree

4 files changed

+228
-0
lines changed

4 files changed

+228
-0
lines changed

Diff for: C4/UI/ScreenRecorder.swift

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright © 2016 C4
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to
5+
// deal in the Software without restriction, including without limitation the
6+
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
// sell copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions: The above copyright
9+
// notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
17+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
18+
// IN THE SOFTWARE.
19+
20+
import ReplayKit
21+
22+
public typealias PreviewControllerFinishedAction = (activities: Set<String>?) -> ()
23+
public typealias RecorderStoppedAction = () -> ()
24+
25+
public class ScreenRecorder: NSObject, RPPreviewViewControllerDelegate {
26+
internal var recorder: RPScreenRecorder?
27+
public var preview: RPPreviewViewController?
28+
public var controller: UIViewController?
29+
30+
public override init() {
31+
super.init()
32+
recorder = RPScreenRecorder.sharedRecorder()
33+
}
34+
35+
public func start() {
36+
guard recorder != nil else {
37+
print("Recorder was not initialized")
38+
return
39+
}
40+
41+
recorder!.startRecordingWithMicrophoneEnabled(false) { error in
42+
if error != nil {
43+
print("Start Recording Error: \(error?.localizedDescription)")
44+
}
45+
}
46+
}
47+
48+
public func start(duration: Double) {
49+
preview = nil
50+
51+
self.start()
52+
delay(duration) {
53+
self.stop()
54+
}
55+
}
56+
57+
public var didStop: RecorderStoppedAction?
58+
59+
public func stop() {
60+
self.recorder!.stopRecordingWithHandler { previewViewController, error in
61+
self.preview = previewViewController
62+
self.preview?.previewControllerDelegate = self
63+
self.didStop?()
64+
}
65+
}
66+
67+
public func showPreview() {
68+
guard controller != nil else {
69+
print("Recorder has no controller in which to present preview.")
70+
return
71+
}
72+
73+
guard let p = preview else {
74+
print("Recorder has no preview to show.")
75+
return
76+
}
77+
78+
self.controller?.presentViewController(p, animated: true, completion: nil)
79+
}
80+
81+
public func presentPreview(viewController: UIViewController) {
82+
guard preview != nil else {
83+
print("PreviewRecorder has no movie to preview.")
84+
return
85+
}
86+
viewController.presentViewController(self.preview!, animated: true, completion: nil)
87+
}
88+
89+
internal func screenRecorder(screenRecorder: RPScreenRecorder, didStopRecordingWithError error: NSError, previewViewController: RPPreviewViewController?) {
90+
}
91+
92+
public var previewFinished: PreviewControllerFinishedAction?
93+
94+
var activities: Set<String>?
95+
public func previewController(previewController: RPPreviewViewController, didFinishWithActivityTypes activityTypes: Set<String>) {
96+
self.activities = activityTypes
97+
}
98+
99+
public func previewControllerDidFinish(previewController: RPPreviewViewController) {
100+
if let f = self.previewFinished {
101+
f(activities: self.activities)
102+
}
103+
if let c = self.controller {
104+
c.dismissViewControllerAnimated(true, completion: nil)
105+
}
106+
}
107+
}

Diff for: C4App/Images.xcassets/AppIcon.appiconset/Contents.json

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
"idiom" : "ipad",
6060
"size" : "76x76",
6161
"scale" : "2x"
62+
},
63+
{
64+
"idiom" : "ipad",
65+
"size" : "83.5x83.5",
66+
"scale" : "2x"
6267
}
6368
],
6469
"info" : {

Diff for: C4App/ViewController.swift

+112
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,119 @@ import C4
2121
import UIKit
2222

2323
class ViewController: CanvasController {
24+
let recorder = ScreenRecorder()
25+
2426
override func setup() {
27+
recorder.controller = self
28+
29+
recorder.didStop = {
30+
self.recorder.presentPreview(self)
31+
}
32+
33+
recorder.previewFinished = { activities in
34+
print(activities)
35+
}
36+
37+
canvas.backgroundColor = black
38+
createInner()
39+
createInnerMask()
40+
createOuter()
41+
positionShapes()
42+
43+
canvas.addLongPressGestureRecognizer { location, state in
44+
self.recorder.start(5.0)
45+
self.startDemo()
46+
}
47+
}
48+
49+
func startDemo() {
50+
initializeDisplayLink()
51+
animateCanvas()
52+
}
53+
54+
var inner: Polygon!
55+
var innerMask: Polygon!
56+
var outer: Polygon!
57+
58+
var pointCount = 90
59+
var radius = (50.0, 58.0)
60+
var lineWidths = (2.0, 0.5)
61+
var primaryColor = C4Blue
62+
63+
func randomize(var points: [Point], var radius r: Double) -> [Point] {
64+
for _ in 0..<points.count {
65+
let index = random(below: points.count)
66+
if random(below: 10) > 6 {
67+
r = distance(Point(), rhs: points[index]) * Double(random(min: 95, max: 105)) / 100.0
68+
}
69+
let θ = Double(index) / 45.0 * M_PI
70+
points[index] = Point(r * sin(θ), r * cos(θ))
71+
}
72+
return points
73+
}
74+
75+
func createInner() {
76+
inner = createPoly(radius: radius.0)
77+
inner.lineWidth = lineWidths.0
78+
inner.fillColor = black
79+
inner.strokeColor = primaryColor
80+
}
81+
82+
func createInnerMask() {
83+
innerMask = createPoly(radius: radius.1)
84+
innerMask.fillColor = primaryColor
85+
inner.mask = innerMask
86+
}
87+
88+
func createOuter() {
89+
outer = Polygon(innerMask.points)
90+
outer.strokeColor = primaryColor
91+
let uic = UIColor(primaryColor)?.colorWithAlphaComponent(0.2)
92+
outer.fillColor = Color(uic!)
93+
outer.lineWidth = lineWidths.1
94+
outer.close()
95+
}
96+
97+
func positionShapes() {
98+
let container = View(frame: Rect(0, 0, 1, 1))
99+
container.add(outer)
100+
container.add(inner)
101+
container.center = canvas.center
102+
canvas.add(container)
103+
}
104+
105+
func initializeDisplayLink() {
106+
let displayLink = CADisplayLink(target: self, selector: "update")
107+
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
108+
}
109+
110+
func animateCanvas() {
111+
let a = ViewAnimation(duration: 1.0) {
112+
self.canvas.transform.rotate(M_PI)
113+
}
114+
a.curve = .Linear
115+
a.repeats = true
116+
a.animate()
117+
}
118+
119+
func update() {
120+
ViewAnimation(duration: 0) {
121+
self.inner.points = self.randomize(self.inner.points, radius: self.radius.1)
122+
let maskPoints = self.randomize(self.innerMask.points, radius: self.radius.0)
123+
self.innerMask.points = maskPoints
124+
self.outer.points = maskPoints
125+
}.animate()
126+
}
25127

128+
func createPoly(radius r: Double) -> Polygon {
129+
var points = [Point]()
130+
for i in 0..<pointCount {
131+
let θ = Double(i) * M_PI * 2.0 / Double(pointCount)
132+
let pt = Point(r * sin(θ), r * cos(θ))
133+
points.append(pt)
134+
}
135+
let poly = Polygon(points)
136+
poly.close()
137+
return poly
26138
}
27139
}

Diff for: C4iOS.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
1F754DCB1C48EE600036D39F /* ImageLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F754DCA1C48EE600036D39F /* ImageLayer.swift */; };
11+
1F75FE951C5CADBF00EB62C2 /* ScreenRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F75FE941C5CADBF00EB62C2 /* ScreenRecorder.swift */; };
1112
1F8252CC1BB3C65E0090E98A /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8252CB1BB3C65E0090E98A /* Timer.swift */; };
1213
1F8A2FEB1C4C24FD0087063A /* View+Render.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8A2FEA1C4C24FD0087063A /* View+Render.swift */; };
1314
1FAB35E81C5372CE00620741 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAB35E71C5372CE00620741 /* Pixel.swift */; };
@@ -129,6 +130,7 @@
129130

130131
/* Begin PBXFileReference section */
131132
1F754DCA1C48EE600036D39F /* ImageLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLayer.swift; sourceTree = "<group>"; };
133+
1F75FE941C5CADBF00EB62C2 /* ScreenRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenRecorder.swift; sourceTree = "<group>"; };
132134
1F8252CB1BB3C65E0090E98A /* Timer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
133135
1F8A2FEA1C4C24FD0087063A /* View+Render.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Render.swift"; sourceTree = "<group>"; };
134136
1F8CB1D91A05781700A20783 /* RectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RectTests.swift; sourceTree = "<group>"; };
@@ -380,6 +382,7 @@
380382
A9D4F6AB1B534F9F00F937AB /* UIViewController+C4View.swift */,
381383
A9BC57261C50ABA80068BBA3 /* UIImage+Color.swift */,
382384
1FAB35E71C5372CE00620741 /* Pixel.swift */,
385+
1F75FE941C5CADBF00EB62C2 /* ScreenRecorder.swift */,
383386
);
384387
path = UI;
385388
sourceTree = "<group>";
@@ -630,6 +633,7 @@
630633
A9D4F6DB1B534F9F00F937AB /* Checkerboard.swift in Sources */,
631634
A9D4F6BB1B534F9F00F937AB /* Arc.swift in Sources */,
632635
A9D4F6D91B534F9F00F937AB /* Wedge.swift in Sources */,
636+
1F75FE951C5CADBF00EB62C2 /* ScreenRecorder.swift in Sources */,
633637
);
634638
runOnlyForDeploymentPostprocessing = 0;
635639
};

0 commit comments

Comments
 (0)