Skip to content

Commit ce82245

Browse files
feat: adds NavigationView, NavigationLink based navigation to our simple SwiftUI app
1 parent d723084 commit ce82245

File tree

4 files changed

+342
-15
lines changed

4 files changed

+342
-15
lines changed

examples/spm/SPMExample/SPMExample.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
2B056E492AB9FF190076A314 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B056E482AB9FF190076A314 /* Preview Assets.xcassets */; };
1414
2B056E502AB9FFDB0076A314 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B056E4F2AB9FFDB0076A314 /* AppDelegate.swift */; };
1515
2B056E562ABA08960076A314 /* NewRelic in Frameworks */ = {isa = PBXBuildFile; productRef = 2B056E552ABA08960076A314 /* NewRelic */; };
16+
2B2253562B4DE74B00EBFFE2 /* UtilViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2253542B4DE74B00EBFFE2 /* UtilViewModel.swift */; };
17+
2B2253572B4DE74B00EBFFE2 /* UtilView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2253552B4DE74B00EBFFE2 /* UtilView.swift */; };
1618
/* End PBXBuildFile section */
1719

1820
/* Begin PBXFileReference section */
@@ -23,6 +25,8 @@
2325
2B056E462AB9FF190076A314 /* SPMExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SPMExample.entitlements; sourceTree = "<group>"; };
2426
2B056E482AB9FF190076A314 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2527
2B056E4F2AB9FFDB0076A314 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
28+
2B2253542B4DE74B00EBFFE2 /* UtilViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilViewModel.swift; sourceTree = "<group>"; };
29+
2B2253552B4DE74B00EBFFE2 /* UtilView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilView.swift; sourceTree = "<group>"; };
2630
/* End PBXFileReference section */
2731

2832
/* Begin PBXFrameworksBuildPhase section */
@@ -59,6 +63,8 @@
5963
2B056E4F2AB9FFDB0076A314 /* AppDelegate.swift */,
6064
2B056E402AB9FF180076A314 /* SPMExampleApp.swift */,
6165
2B056E422AB9FF180076A314 /* ContentView.swift */,
66+
2B2253552B4DE74B00EBFFE2 /* UtilView.swift */,
67+
2B2253542B4DE74B00EBFFE2 /* UtilViewModel.swift */,
6268
2B056E442AB9FF190076A314 /* Assets.xcassets */,
6369
2B056E462AB9FF190076A314 /* SPMExample.entitlements */,
6470
2B056E472AB9FF190076A314 /* Preview Content */,
@@ -173,9 +179,11 @@
173179
isa = PBXSourcesBuildPhase;
174180
buildActionMask = 2147483647;
175181
files = (
182+
2B2253562B4DE74B00EBFFE2 /* UtilViewModel.swift in Sources */,
176183
2B056E502AB9FFDB0076A314 /* AppDelegate.swift in Sources */,
177184
2B056E432AB9FF180076A314 /* ContentView.swift in Sources */,
178185
2B056E412AB9FF180076A314 /* SPMExampleApp.swift in Sources */,
186+
2B2253572B4DE74B00EBFFE2 /* UtilView.swift in Sources */,
179187
);
180188
runOnlyForDeploymentPostprocessing = 0;
181189
};

examples/spm/SPMExample/SPMExample/ContentView.swift

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,20 @@ import NewRelic
1010

1111
struct ContentView: View {
1212
var body: some View {
13-
VStack {
14-
Image(systemName: "globe")
15-
.imageScale(.large)
16-
Text("Hello, world!")
17-
18-
Button {
19-
crash()
20-
} label: {
21-
Text("Test crash")
13+
NavigationView {
14+
VStack {
15+
Image(systemName: "globe")
16+
.imageScale(.large)
17+
Text("Hello, world!")
18+
}
19+
.navigationBarTitle("SPMExample")
20+
.toolbar {
21+
HStack {
22+
NavigationLink(destination: UtilityView(viewModel: UtilityView.ViewModel())) { Text("Utilities") }
23+
}
2224
}
2325
}
24-
.padding()
25-
}
26-
27-
func crash() {
28-
// This will cause a crash to test the crash uploader, crash files will not get recorded if the debugger is running.
29-
NewRelic.crashNow("New Relic intentionally crashed to test Utils")
26+
.NRTrackView(name: "ContentView")
3027
}
3128
}
3229

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//
2+
// UtilView.swift
3+
// testApp (iOS)
4+
//
5+
// Created by Anna Huller on 6/14/22.
6+
//
7+
8+
import SwiftUI
9+
import NewRelic
10+
11+
struct UtilityView: View {
12+
13+
@StateObject var viewModel: ViewModel
14+
15+
var body: some View {
16+
VStack {
17+
HStack {
18+
var label = "Breadcrumbs:" + String(viewModel.numBreadcrumbs)
19+
Text(label)
20+
VStack {
21+
Button("Add Valid Breadcrumb") {
22+
viewModel.makeEvent()
23+
viewModel.makeBreadcrumb(name: "test", attributes: ["button" : "Breadcrumb"])
24+
label = "Breadcrumbs:" + String(viewModel.numBreadcrumbs)
25+
}
26+
27+
Button("Add Invalid Breadcrumb") {
28+
viewModel.makeEvent()
29+
viewModel.makeBreadcrumb(name: "", attributes: ["button" : "Breadcrumb"])
30+
label = "Breadcrumbs:" + String(viewModel.numBreadcrumbs)
31+
}
32+
}
33+
}
34+
HStack {
35+
var label = "Attributes: " + viewModel.attributes
36+
Text(label)
37+
Button("Set Attributes!") {
38+
viewModel.makeEvent()
39+
viewModel.setAttributes()
40+
label = viewModel.attributes
41+
}
42+
}
43+
44+
Button("Crash Now!") {
45+
viewModel.makeEvent()
46+
viewModel.crash()
47+
}
48+
Button("Make Huge Crash Report!") {
49+
viewModel.makeEvent()
50+
viewModel.hugeCrashReport()
51+
}
52+
Button("Remove Attributes!") {
53+
viewModel.makeEvent()
54+
55+
if viewModel.removeAttributes() == true {
56+
viewModel.attributes = ""
57+
58+
}
59+
}
60+
Button("Record Error") {
61+
viewModel.makeError()
62+
viewModel.makeEvent()
63+
}
64+
65+
Group {
66+
let label = "Button Presses: " + String(viewModel.events)
67+
Text(label)
68+
Button("Make 100 events") {
69+
viewModel.make100Events()
70+
}
71+
Button("START Interaction Trace") {
72+
viewModel.startInteractionTrace()
73+
}
74+
Button("END Interaction Trace") {
75+
viewModel.stopInteractionTrace()
76+
77+
}
78+
Button("Send Redirect Request") {
79+
viewModel.sendRedirectRequest()
80+
}
81+
Button("Notice Network Request") {
82+
viewModel.noticeNWRequest()
83+
}
84+
Button("Notice Network Failure") {
85+
viewModel.noticeFailedNWRequest()
86+
}
87+
88+
Button("URLSession dataTask") {
89+
viewModel.doDataTask()
90+
}
91+
92+
Button("URLSession dataTask w/ completion") {
93+
viewModel.doDataTaskWithCompletionHandler()
94+
}
95+
}
96+
}
97+
.navigationBarTitle(viewModel.title)
98+
.NRTrackView(name: "UtilityView")
99+
}
100+
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
//
2+
// UtilViewModel.swift
3+
// testApp (iOS)
4+
//
5+
// Created by Anna Huller on 6/14/22.
6+
//
7+
8+
import SwiftUI
9+
import NewRelic
10+
11+
extension UtilityView {
12+
@MainActor class ViewModel: ObservableObject {
13+
14+
let title = "Utility"
15+
@Published var numBreadcrumbs = 0
16+
@Published var goodAttribute = false
17+
@Published var badAttribute = false
18+
@Published var attributes = ""
19+
@Published var events = 0
20+
21+
var uniqueInteractionTraceIdentifier: String? = nil
22+
23+
let taskProcessor = TaskProcessor()
24+
25+
func crash() {
26+
NewRelic.crashNow("New Relic intentionally crashed to test Utils")
27+
}
28+
func hugeCrashReport() {
29+
let crashOutputFilePath = String(format: "%@%@/%d.%@", NSTemporaryDirectory(), "nrcrashreports", 42, "nrcrashreport")
30+
31+
let data = makeBigDictionary()
32+
do {
33+
try FileManager.default.createDirectory(atPath: String(format: "%@/%@", NSTemporaryDirectory(), "nrcrashreports"), withIntermediateDirectories: true)
34+
35+
let success = FileManager.default.createFile(atPath: crashOutputFilePath, contents: data)
36+
37+
} catch {
38+
print(error.localizedDescription)
39+
}
40+
}
41+
func removeAttributes() -> Bool{
42+
return NewRelic.removeAllAttributes()
43+
}
44+
func setAttributes(){
45+
attributes = "test1: " + String(NewRelic.setAttribute("test1", value: 1)) + " '': " + String(NewRelic.setAttribute("", value: 2))
46+
}
47+
func makeError(){
48+
do {
49+
try errorMethod()
50+
} catch {
51+
NewRelic.recordError(error)
52+
53+
}
54+
}
55+
private func errorMethod() throws {
56+
throw CancellationError.init()
57+
}
58+
func makeBreadcrumb(name: String, attributes: Dictionary<String, Any>){
59+
let madeBreadCrumb = NewRelic.recordBreadcrumb(name,
60+
attributes: attributes)
61+
if madeBreadCrumb == true {
62+
self.numBreadcrumbs += 1
63+
}
64+
}
65+
66+
func makeEvent(){
67+
let madeEvent = NewRelic.recordCustomEvent("ButtonPress")
68+
if madeEvent == true {
69+
events += 1
70+
}
71+
}
72+
73+
func make100Events() {
74+
for i in 0...100 {
75+
NewRelic.recordCustomEvent("ButtonPress")
76+
}
77+
}
78+
79+
func sendRedirectRequest() {
80+
guard let url = URL(string: "https://easynvest.com.br") else { return }
81+
82+
var request = URLRequest(url: url)
83+
request.httpMethod = "GET"
84+
85+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
86+
print("ok")
87+
}
88+
task.resume()
89+
}
90+
func stopInteractionTrace() {
91+
guard let identifier = uniqueInteractionTraceIdentifier else {
92+
print("no interaction to stop...")
93+
return
94+
}
95+
NewRelic.stopCurrentInteraction(identifier)
96+
97+
uniqueInteractionTraceIdentifier = nil
98+
}
99+
100+
func startInteractionTrace() {
101+
uniqueInteractionTraceIdentifier = NewRelic.startInteraction(withName: "myInteractionName")
102+
}
103+
104+
105+
106+
func noticeFailedNWRequest() {
107+
NewRelic.noticeNetworkFailure(for: URL(string: "https://www.google.com"), httpMethod: "GET",
108+
with: NRTimer(), andFailureCode: NSURLErrorTimedOut)
109+
}
110+
111+
func noticeNWRequest() {
112+
// NewRelic.noticeNetworkRequest(for: URL(string: "https://www.google.com"), httpMethod: "GET", with: NRTimer(), responseHeaders: [:],
113+
// statusCode: 200, bytesSent: 1000, bytesReceived: 1000, responseData: Data(), traceHeaders: nil, andParams: nil)
114+
115+
NewRelic.noticeNetworkRequest(for: URL(string:"https://fakeurl.com"),
116+
httpMethod: "GET",
117+
startTime: Date().timeIntervalSince1970,
118+
endTime: Date().timeIntervalSince1970,
119+
responseHeaders: nil,
120+
statusCode: 400,
121+
bytesSent: 100,
122+
bytesReceived: 200,
123+
responseData: Data("example response body".utf8),
124+
traceHeaders: nil,
125+
andParams: nil)
126+
}
127+
128+
func setBuild() {
129+
NewRelic.setApplicationBuild("42")
130+
}
131+
132+
133+
func doDataTask() {
134+
let urlSession = URLSession(configuration: URLSession.shared.configuration, delegate: taskProcessor, delegateQueue: nil)
135+
guard let url = URL(string: "https://www.google.com") else { return }
136+
137+
let request = URLRequest(url: url)
138+
139+
let dataTask = urlSession.dataTask(with: request)
140+
141+
dataTask.resume()
142+
}
143+
144+
func doDataTaskWithCompletionHandler() {
145+
let urlSession = URLSession(configuration: URLSessionConfiguration.default)
146+
guard let url = URL(string: "https://www.google.com") else { return }
147+
148+
let request = URLRequest(url: url)
149+
150+
let dataTask = urlSession.dataTask(with: request) { data, response, error in
151+
//Handle
152+
if let httpResponse = response as? HTTPURLResponse {
153+
print("SUCCESS w/ dataTask w/ completionHandler")
154+
155+
}
156+
else if let errorCode = error?._code {
157+
print(error?.localizedDescription)
158+
159+
}
160+
else {
161+
162+
}
163+
}
164+
165+
dataTask.resume()
166+
urlSession.finishTasksAndInvalidate()
167+
}
168+
169+
func makeBigDictionary() -> Data {
170+
var dictionary = [String:String]()
171+
var data = Data()
172+
for i in 0...30000 {
173+
dictionary.updateValue("42", forKey: "The meaning of life #"+String(i))
174+
}
175+
do {
176+
data = try JSONSerialization.data(withJSONObject: dictionary)
177+
} catch {
178+
print(error.localizedDescription)
179+
}
180+
return data
181+
}
182+
}
183+
}
184+
185+
class TaskProcessor: NSObject, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
186+
187+
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
188+
189+
completionHandler(.allow)
190+
}
191+
192+
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
193+
print("DataTask rcv data.")
194+
}
195+
196+
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
197+
print("DataTask did complete.")
198+
}
199+
200+
public func urlSession(_ session: URLSession,
201+
task: URLSessionTask,
202+
didReceive challenge: URLAuthenticationChallenge,
203+
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
204+
// completionHandler(.performDefaultHandling, nil)
205+
206+
//completionHandler(.cancelAuthenticationChallenge, nil)
207+
208+
if let trust = challenge.protectionSpace.serverTrust,
209+
SecTrustGetCertificateCount(trust) > 0 {
210+
if let certificate = SecTrustGetCertificateAtIndex(trust, 0) {
211+
let data = SecCertificateCopyData(certificate) as Data
212+
213+
completionHandler(.useCredential, URLCredential(trust: trust))
214+
return
215+
}
216+
217+
}
218+
completionHandler(.cancelAuthenticationChallenge, nil)
219+
220+
221+
}
222+
}

0 commit comments

Comments
 (0)