Skip to content

Commit

Permalink
feat: adds NavigationView, NavigationLink based navigation to our sim…
Browse files Browse the repository at this point in the history
…ple SwiftUI app
  • Loading branch information
cdillard-NewRelic committed Jan 9, 2024
1 parent d723084 commit ce82245
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 15 deletions.
8 changes: 8 additions & 0 deletions examples/spm/SPMExample/SPMExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
2B056E492AB9FF190076A314 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B056E482AB9FF190076A314 /* Preview Assets.xcassets */; };
2B056E502AB9FFDB0076A314 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B056E4F2AB9FFDB0076A314 /* AppDelegate.swift */; };
2B056E562ABA08960076A314 /* NewRelic in Frameworks */ = {isa = PBXBuildFile; productRef = 2B056E552ABA08960076A314 /* NewRelic */; };
2B2253562B4DE74B00EBFFE2 /* UtilViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2253542B4DE74B00EBFFE2 /* UtilViewModel.swift */; };
2B2253572B4DE74B00EBFFE2 /* UtilView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2253552B4DE74B00EBFFE2 /* UtilView.swift */; };
/* End PBXBuildFile section */

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

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -59,6 +63,8 @@
2B056E4F2AB9FFDB0076A314 /* AppDelegate.swift */,
2B056E402AB9FF180076A314 /* SPMExampleApp.swift */,
2B056E422AB9FF180076A314 /* ContentView.swift */,
2B2253552B4DE74B00EBFFE2 /* UtilView.swift */,
2B2253542B4DE74B00EBFFE2 /* UtilViewModel.swift */,
2B056E442AB9FF190076A314 /* Assets.xcassets */,
2B056E462AB9FF190076A314 /* SPMExample.entitlements */,
2B056E472AB9FF190076A314 /* Preview Content */,
Expand Down Expand Up @@ -173,9 +179,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2B2253562B4DE74B00EBFFE2 /* UtilViewModel.swift in Sources */,
2B056E502AB9FFDB0076A314 /* AppDelegate.swift in Sources */,
2B056E432AB9FF180076A314 /* ContentView.swift in Sources */,
2B056E412AB9FF180076A314 /* SPMExampleApp.swift in Sources */,
2B2253572B4DE74B00EBFFE2 /* UtilView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
27 changes: 12 additions & 15 deletions examples/spm/SPMExample/SPMExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,20 @@ import NewRelic

struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
Text("Hello, world!")

Button {
crash()
} label: {
Text("Test crash")
NavigationView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
Text("Hello, world!")
}
.navigationBarTitle("SPMExample")
.toolbar {
HStack {
NavigationLink(destination: UtilityView(viewModel: UtilityView.ViewModel())) { Text("Utilities") }
}
}
}
.padding()
}

func crash() {
// This will cause a crash to test the crash uploader, crash files will not get recorded if the debugger is running.
NewRelic.crashNow("New Relic intentionally crashed to test Utils")
.NRTrackView(name: "ContentView")
}
}

Expand Down
100 changes: 100 additions & 0 deletions examples/spm/SPMExample/SPMExample/UtilView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// UtilView.swift
// testApp (iOS)
//
// Created by Anna Huller on 6/14/22.
//

import SwiftUI
import NewRelic

struct UtilityView: View {

@StateObject var viewModel: ViewModel

var body: some View {
VStack {
HStack {
var label = "Breadcrumbs:" + String(viewModel.numBreadcrumbs)
Text(label)
VStack {
Button("Add Valid Breadcrumb") {
viewModel.makeEvent()
viewModel.makeBreadcrumb(name: "test", attributes: ["button" : "Breadcrumb"])
label = "Breadcrumbs:" + String(viewModel.numBreadcrumbs)
}

Button("Add Invalid Breadcrumb") {
viewModel.makeEvent()
viewModel.makeBreadcrumb(name: "", attributes: ["button" : "Breadcrumb"])
label = "Breadcrumbs:" + String(viewModel.numBreadcrumbs)
}
}
}
HStack {
var label = "Attributes: " + viewModel.attributes
Text(label)
Button("Set Attributes!") {
viewModel.makeEvent()
viewModel.setAttributes()
label = viewModel.attributes
}
}

Button("Crash Now!") {
viewModel.makeEvent()
viewModel.crash()
}
Button("Make Huge Crash Report!") {
viewModel.makeEvent()
viewModel.hugeCrashReport()
}
Button("Remove Attributes!") {
viewModel.makeEvent()

if viewModel.removeAttributes() == true {
viewModel.attributes = ""

}
}
Button("Record Error") {
viewModel.makeError()
viewModel.makeEvent()
}

Group {
let label = "Button Presses: " + String(viewModel.events)
Text(label)
Button("Make 100 events") {
viewModel.make100Events()
}
Button("START Interaction Trace") {
viewModel.startInteractionTrace()
}
Button("END Interaction Trace") {
viewModel.stopInteractionTrace()

}
Button("Send Redirect Request") {
viewModel.sendRedirectRequest()
}
Button("Notice Network Request") {
viewModel.noticeNWRequest()
}
Button("Notice Network Failure") {
viewModel.noticeFailedNWRequest()
}

Button("URLSession dataTask") {
viewModel.doDataTask()
}

Button("URLSession dataTask w/ completion") {
viewModel.doDataTaskWithCompletionHandler()
}
}
}
.navigationBarTitle(viewModel.title)
.NRTrackView(name: "UtilityView")
}
}
222 changes: 222 additions & 0 deletions examples/spm/SPMExample/SPMExample/UtilViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
//
// UtilViewModel.swift
// testApp (iOS)
//
// Created by Anna Huller on 6/14/22.
//

import SwiftUI
import NewRelic

extension UtilityView {
@MainActor class ViewModel: ObservableObject {

let title = "Utility"
@Published var numBreadcrumbs = 0
@Published var goodAttribute = false
@Published var badAttribute = false
@Published var attributes = ""
@Published var events = 0

var uniqueInteractionTraceIdentifier: String? = nil

let taskProcessor = TaskProcessor()

func crash() {
NewRelic.crashNow("New Relic intentionally crashed to test Utils")
}
func hugeCrashReport() {
let crashOutputFilePath = String(format: "%@%@/%d.%@", NSTemporaryDirectory(), "nrcrashreports", 42, "nrcrashreport")

let data = makeBigDictionary()
do {
try FileManager.default.createDirectory(atPath: String(format: "%@/%@", NSTemporaryDirectory(), "nrcrashreports"), withIntermediateDirectories: true)

let success = FileManager.default.createFile(atPath: crashOutputFilePath, contents: data)

} catch {
print(error.localizedDescription)
}
}
func removeAttributes() -> Bool{
return NewRelic.removeAllAttributes()
}
func setAttributes(){
attributes = "test1: " + String(NewRelic.setAttribute("test1", value: 1)) + " '': " + String(NewRelic.setAttribute("", value: 2))
}
func makeError(){
do {
try errorMethod()
} catch {
NewRelic.recordError(error)

}
}
private func errorMethod() throws {
throw CancellationError.init()
}
func makeBreadcrumb(name: String, attributes: Dictionary<String, Any>){
let madeBreadCrumb = NewRelic.recordBreadcrumb(name,
attributes: attributes)
if madeBreadCrumb == true {
self.numBreadcrumbs += 1
}
}

func makeEvent(){
let madeEvent = NewRelic.recordCustomEvent("ButtonPress")
if madeEvent == true {
events += 1
}
}

func make100Events() {
for i in 0...100 {
NewRelic.recordCustomEvent("ButtonPress")
}
}

func sendRedirectRequest() {
guard let url = URL(string: "https://easynvest.com.br") else { return }

var request = URLRequest(url: url)
request.httpMethod = "GET"

let task = URLSession.shared.dataTask(with: request) { data, response, error in
print("ok")
}
task.resume()
}
func stopInteractionTrace() {
guard let identifier = uniqueInteractionTraceIdentifier else {
print("no interaction to stop...")
return
}
NewRelic.stopCurrentInteraction(identifier)

uniqueInteractionTraceIdentifier = nil
}

func startInteractionTrace() {
uniqueInteractionTraceIdentifier = NewRelic.startInteraction(withName: "myInteractionName")
}



func noticeFailedNWRequest() {
NewRelic.noticeNetworkFailure(for: URL(string: "https://www.google.com"), httpMethod: "GET",
with: NRTimer(), andFailureCode: NSURLErrorTimedOut)
}

func noticeNWRequest() {
// NewRelic.noticeNetworkRequest(for: URL(string: "https://www.google.com"), httpMethod: "GET", with: NRTimer(), responseHeaders: [:],
// statusCode: 200, bytesSent: 1000, bytesReceived: 1000, responseData: Data(), traceHeaders: nil, andParams: nil)

NewRelic.noticeNetworkRequest(for: URL(string:"https://fakeurl.com"),
httpMethod: "GET",
startTime: Date().timeIntervalSince1970,
endTime: Date().timeIntervalSince1970,
responseHeaders: nil,
statusCode: 400,
bytesSent: 100,
bytesReceived: 200,
responseData: Data("example response body".utf8),
traceHeaders: nil,
andParams: nil)
}

func setBuild() {
NewRelic.setApplicationBuild("42")
}


func doDataTask() {
let urlSession = URLSession(configuration: URLSession.shared.configuration, delegate: taskProcessor, delegateQueue: nil)
guard let url = URL(string: "https://www.google.com") else { return }

let request = URLRequest(url: url)

let dataTask = urlSession.dataTask(with: request)

dataTask.resume()
}

func doDataTaskWithCompletionHandler() {
let urlSession = URLSession(configuration: URLSessionConfiguration.default)
guard let url = URL(string: "https://www.google.com") else { return }

let request = URLRequest(url: url)

let dataTask = urlSession.dataTask(with: request) { data, response, error in
//Handle
if let httpResponse = response as? HTTPURLResponse {
print("SUCCESS w/ dataTask w/ completionHandler")

}
else if let errorCode = error?._code {
print(error?.localizedDescription)

}
else {

}
}

dataTask.resume()
urlSession.finishTasksAndInvalidate()
}

func makeBigDictionary() -> Data {
var dictionary = [String:String]()
var data = Data()
for i in 0...30000 {
dictionary.updateValue("42", forKey: "The meaning of life #"+String(i))
}
do {
data = try JSONSerialization.data(withJSONObject: dictionary)
} catch {
print(error.localizedDescription)
}
return data
}
}
}

class TaskProcessor: NSObject, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {

public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {

completionHandler(.allow)
}

public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("DataTask rcv data.")
}

public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("DataTask did complete.")
}

public func urlSession(_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// completionHandler(.performDefaultHandling, nil)

//completionHandler(.cancelAuthenticationChallenge, nil)

if let trust = challenge.protectionSpace.serverTrust,
SecTrustGetCertificateCount(trust) > 0 {
if let certificate = SecTrustGetCertificateAtIndex(trust, 0) {
let data = SecCertificateCopyData(certificate) as Data

completionHandler(.useCredential, URLCredential(trust: trust))
return
}

}
completionHandler(.cancelAuthenticationChallenge, nil)


}
}

0 comments on commit ce82245

Please sign in to comment.