[{"data":1,"prerenderedAt":1760},["ShallowReactive",2],{"Categories":3,"NavIndexCategoriesCountFooter":203,"content-\u002F2020\u002F03\u002F04\u002Fview-refactor-and-save-to-keychain\u002F":204},[4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,68,70,71,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,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202],{"category":5},"System Administration",{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},"Software Development",{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":27},{"category":27},{"category":67},"Drones & RC",{"category":69},"DIY Projects",{"category":67},{"category":72},"Photography",{"category":69},{"category":69},{"category":69},{"category":67},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":67},{"category":69},{"category":69},{"category":67},{"category":67},{"category":72},{"category":72},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":5},{"category":5},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":5},{"category":67},{"category":67},{"category":72},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":72},{"category":67},{"category":138},"3D Printing - Laser Cutting - CNC",{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":5},{"category":138},{"category":27},{"category":27},{"category":138},{"category":138},{"category":72},{"category":158},"Photography,3D Printing - Laser Cutting - CNC",{"category":27},{"category":27},{"category":69},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":67},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":27},{"category":69},{"category":27},{"category":27},{"category":27},{"category":27},{"category":181},"Home Assistant",{"category":181},{"category":72},{"category":27},{"category":27},{"category":72},{"category":138},{"category":5},{"category":72},{"category":72},{"category":138},{"category":27},{"category":181},{"category":181},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},191,{"id":205,"title":206,"body":207,"category":27,"date":1746,"description":213,"embedImage":1747,"extension":1748,"image":1747,"intro":213,"meta":1749,"navigation":374,"path":1750,"seo":1751,"series":1752,"sitemap":1753,"stem":1754,"tags":1755,"__hash__":1759},"content\u002F2020\u002F03\u002F04\u002Fview-refactor-and-save-to-keychain.md","View refactor and save to keychain",{"type":208,"value":209,"toc":1733},"minimark",[210,214,217,222,225,228,238,241,246,249,252,282,289,293,296,299,302,305,308,423,426,429,806,809,811,815,818,829,832,836,839,1061,1065,1079,1086,1089,1324,1327,1331,1356,1359,1433,1437,1440,1443,1708,1711,1713,1717,1720,1722,1729],[211,212,213],"p",{},"At this stage - we've a lot of stuff in the main ContentView. Let's do something about that first.",[215,216],"hr",{},[218,219,221],"h2",{"id":220},"refactor","Refactor",[211,223,224],{},"We'll start by moving the CodeScanner stuff to its own view. We'll also give the user a cancellation option so that they can clear the view without scanning.",[211,226,227],{},"To do this we need two things:",[229,230,231,235],"ul",{},[232,233,234],"li",{},"A way to close the view",[232,236,237],{},"A way to return the new data",[211,239,240],{},"Both of these could be done by passing in a state variable from the controlling view. However - we'll take a look at some other options.",[242,243,245],"h3",{"id":244},"closing-the-view","Closing the view",[211,247,248],{},"A view can get some information on its presentation from the environment.",[211,250,251],{},"In the new view - we can add this to the struct:",[253,254,259],"pre",{"className":255,"code":256,"language":257,"meta":258,"style":258},"language-swift shiki shiki-themes github-dark","Environment(\\.presentationMode) var presentation\n","swift","",[260,261,262],"code",{"__ignoreMap":258},[263,264,267,271,275,279],"span",{"class":265,"line":266},"line",1,[263,268,270],{"class":269},"sDLfK","Environment",[263,272,274],{"class":273},"s95oV","(\\.presentationMode) ",[263,276,278],{"class":277},"snl16","var",[263,280,281],{"class":273}," presentation\n",[211,283,284,285,288],{},"Then anywhere we need to close the view - we can call ",[260,286,287],{},"self.presentation.wrappedValue.dismiss()",". In this case - where we used a sheet with a controlling state variable for show\u002Fhide it will update this state for us.",[242,290,292],{"id":291},"returning-the-value","Returning the value",[211,294,295],{},"There are several possibilities here. We could pass in a state variable from the ContentView as a Binding - but - we want to do something with it when the value changes - and what we want to do is not really the concern of the ScannerView - it simply should scan and return the data.",[211,297,298],{},"A variable marked @State can't have hooks on its change events (willSet\u002FdidSet). We could get around this by using the ObservableObject support.",[211,300,301],{},"But - is this value actually a form of state for the content view? Not really. There is no \"initial state\" to send in and on return we want to save it to a data store (the retrieved value from the store is likely app state - but not the string representation of the scanned data itself).",[211,303,304],{},"There is a third option (callback closure) - but - can we use Binding to our advantage here?",[211,306,307],{},"Consider the following code:",[253,309,311],{"className":255,"code":310,"language":257,"meta":258,"style":258},"ScannerView(scannedData: Binding(\n    get: { \"\" },\n    set: self.newScanData\n))\n\n...\n\nfunc newScanData(_ code: String) {\n    ...\n}\n",[260,312,313,333,349,363,369,376,382,387,411,417],{"__ignoreMap":258},[263,314,315,318,321,324,327,330],{"class":265,"line":266},[263,316,317],{"class":269},"ScannerView",[263,319,320],{"class":273},"(",[263,322,323],{"class":269},"scannedData",[263,325,326],{"class":273},": ",[263,328,329],{"class":269},"Binding",[263,331,332],{"class":273},"(\n",[263,334,336,339,342,346],{"class":265,"line":335},2,[263,337,338],{"class":269},"    get",[263,340,341],{"class":273},": { ",[263,343,345],{"class":344},"sU2Wk","\"\"",[263,347,348],{"class":273}," },\n",[263,350,352,355,357,360],{"class":265,"line":351},3,[263,353,354],{"class":269},"    set",[263,356,326],{"class":273},[263,358,359],{"class":269},"self",[263,361,362],{"class":273},".newScanData\n",[263,364,366],{"class":265,"line":365},4,[263,367,368],{"class":273},"))\n",[263,370,372],{"class":265,"line":371},5,[263,373,375],{"emptyLinePlaceholder":374},true,"\n",[263,377,379],{"class":265,"line":378},6,[263,380,381],{"class":277},"...\n",[263,383,385],{"class":265,"line":384},7,[263,386,375],{"emptyLinePlaceholder":374},[263,388,390,393,397,399,402,405,408],{"class":265,"line":389},8,[263,391,392],{"class":277},"func",[263,394,396],{"class":395},"svObZ"," newScanData",[263,398,320],{"class":273},[263,400,401],{"class":395},"_",[263,403,404],{"class":273}," code: ",[263,406,407],{"class":269},"String",[263,409,410],{"class":273},") {\n",[263,412,414],{"class":265,"line":413},9,[263,415,416],{"class":277},"    ...\n",[263,418,420],{"class":265,"line":419},10,[263,421,422],{"class":273},"}\n",[211,424,425],{},"This allows us to specify in the scanner view a normal binding and just set it on scan success - which will call the set here and hand off the value to the newScanData function.",[211,427,428],{},"So - our new ScannerView (with added title and cancel button) looks like this:",[253,430,432],{"className":255,"code":431,"language":257,"meta":258,"style":258},"struct ScannerView: View {\n    @Environment(\\.presentationMode) var presentation\n\n    @Binding var scannedData : String\n\n    var body: some View {\n        VStack {\n            HStack {\n                Spacer()\n                Text(\"Scan\").font(.title).padding(.leading, 16.0)\n                Spacer()\n                Image(systemName: \"xmark.square\")\n                    .resizable()\n                    .frame(width: 32, height: 32)\n                    .onTapGesture {\n                        self.presentation.wrappedValue.dismiss()\n                    }\n            }.padding()\n\n            CodeScannerView(codeTypes: [.qr], simulatedData: \"-\") { result in\n                switch result {\n                case .success(let code):\n                    self.scannedData = code\n                case .failure(let error):\n                    print(error)\n                }\n                self.presentation.wrappedValue.dismiss()\n            }\n        }\n    }\n}\n",[260,433,434,450,461,465,479,483,497,505,512,520,551,558,576,587,617,627,641,647,657,662,690,699,722,737,756,765,771,783,789,795,801],{"__ignoreMap":258},[263,435,436,439,442,444,447],{"class":265,"line":266},[263,437,438],{"class":277},"struct",[263,440,441],{"class":395}," ScannerView",[263,443,326],{"class":273},[263,445,446],{"class":395},"View ",[263,448,449],{"class":273},"{\n",[263,451,452,455,457,459],{"class":265,"line":335},[263,453,454],{"class":277},"    @Environment",[263,456,274],{"class":273},[263,458,278],{"class":277},[263,460,281],{"class":273},[263,462,463],{"class":265,"line":351},[263,464,375],{"emptyLinePlaceholder":374},[263,466,467,470,473,476],{"class":265,"line":365},[263,468,469],{"class":277},"    @Binding",[263,471,472],{"class":277}," var",[263,474,475],{"class":273}," scannedData : ",[263,477,478],{"class":269},"String\n",[263,480,481],{"class":265,"line":371},[263,482,375],{"emptyLinePlaceholder":374},[263,484,485,488,491,494],{"class":265,"line":378},[263,486,487],{"class":277},"    var",[263,489,490],{"class":273}," body: ",[263,492,493],{"class":277},"some",[263,495,496],{"class":273}," View {\n",[263,498,499,502],{"class":265,"line":384},[263,500,501],{"class":269},"        VStack",[263,503,504],{"class":273}," {\n",[263,506,507,510],{"class":265,"line":389},[263,508,509],{"class":269},"            HStack",[263,511,504],{"class":273},[263,513,514,517],{"class":265,"line":413},[263,515,516],{"class":269},"                Spacer",[263,518,519],{"class":273},"()\n",[263,521,522,525,527,530,533,536,539,542,545,548],{"class":265,"line":419},[263,523,524],{"class":269},"                Text",[263,526,320],{"class":273},[263,528,529],{"class":344},"\"Scan\"",[263,531,532],{"class":273},").",[263,534,535],{"class":269},"font",[263,537,538],{"class":273},"(.title).",[263,540,541],{"class":269},"padding",[263,543,544],{"class":273},"(.leading, ",[263,546,547],{"class":269},"16.0",[263,549,550],{"class":273},")\n",[263,552,554,556],{"class":265,"line":553},11,[263,555,516],{"class":269},[263,557,519],{"class":273},[263,559,561,564,566,569,571,574],{"class":265,"line":560},12,[263,562,563],{"class":269},"                Image",[263,565,320],{"class":273},[263,567,568],{"class":269},"systemName",[263,570,326],{"class":273},[263,572,573],{"class":344},"\"xmark.square\"",[263,575,550],{"class":273},[263,577,579,582,585],{"class":265,"line":578},13,[263,580,581],{"class":273},"                    .",[263,583,584],{"class":269},"resizable",[263,586,519],{"class":273},[263,588,590,592,595,597,600,602,605,608,611,613,615],{"class":265,"line":589},14,[263,591,581],{"class":273},[263,593,594],{"class":269},"frame",[263,596,320],{"class":273},[263,598,599],{"class":269},"width",[263,601,326],{"class":273},[263,603,604],{"class":269},"32",[263,606,607],{"class":273},", ",[263,609,610],{"class":269},"height",[263,612,326],{"class":273},[263,614,604],{"class":269},[263,616,550],{"class":273},[263,618,620,622,625],{"class":265,"line":619},15,[263,621,581],{"class":273},[263,623,624],{"class":269},"onTapGesture",[263,626,504],{"class":273},[263,628,630,633,636,639],{"class":265,"line":629},16,[263,631,632],{"class":269},"                        self",[263,634,635],{"class":273},".presentation.wrappedValue.",[263,637,638],{"class":269},"dismiss",[263,640,519],{"class":273},[263,642,644],{"class":265,"line":643},17,[263,645,646],{"class":273},"                    }\n",[263,648,650,653,655],{"class":265,"line":649},18,[263,651,652],{"class":273},"            }.",[263,654,541],{"class":269},[263,656,519],{"class":273},[263,658,660],{"class":265,"line":659},19,[263,661,375],{"emptyLinePlaceholder":374},[263,663,665,668,670,673,676,679,681,684,687],{"class":265,"line":664},20,[263,666,667],{"class":269},"            CodeScannerView",[263,669,320],{"class":273},[263,671,672],{"class":269},"codeTypes",[263,674,675],{"class":273},": [.qr], ",[263,677,678],{"class":269},"simulatedData",[263,680,326],{"class":273},[263,682,683],{"class":344},"\"-\"",[263,685,686],{"class":273},") { result ",[263,688,689],{"class":277},"in\n",[263,691,693,696],{"class":265,"line":692},21,[263,694,695],{"class":277},"                switch",[263,697,698],{"class":273}," result {\n",[263,700,702,705,708,711,713,716,719],{"class":265,"line":701},22,[263,703,704],{"class":277},"                case",[263,706,707],{"class":273}," .",[263,709,710],{"class":269},"success",[263,712,320],{"class":273},[263,714,715],{"class":277},"let",[263,717,718],{"class":273}," code)",[263,720,721],{"class":277},":\n",[263,723,725,728,731,734],{"class":265,"line":724},23,[263,726,727],{"class":269},"                    self",[263,729,730],{"class":273},".scannedData ",[263,732,733],{"class":277},"=",[263,735,736],{"class":273}," code\n",[263,738,740,742,744,747,749,751,754],{"class":265,"line":739},24,[263,741,704],{"class":277},[263,743,707],{"class":273},[263,745,746],{"class":269},"failure",[263,748,320],{"class":273},[263,750,715],{"class":277},[263,752,753],{"class":273}," error)",[263,755,721],{"class":277},[263,757,759,762],{"class":265,"line":758},25,[263,760,761],{"class":269},"                    print",[263,763,764],{"class":273},"(error)\n",[263,766,768],{"class":265,"line":767},26,[263,769,770],{"class":273},"                }\n",[263,772,774,777,779,781],{"class":265,"line":773},27,[263,775,776],{"class":269},"                self",[263,778,635],{"class":273},[263,780,638],{"class":269},[263,782,519],{"class":273},[263,784,786],{"class":265,"line":785},28,[263,787,788],{"class":273},"            }\n",[263,790,792],{"class":265,"line":791},29,[263,793,794],{"class":273},"        }\n",[263,796,798],{"class":265,"line":797},30,[263,799,800],{"class":273},"    }\n",[263,802,804],{"class":265,"line":803},31,[263,805,422],{"class":273},[211,807,808],{},"Back in the ContentView - we have a function that receives the data and can perform the decoding etc there.",[215,810],{},[218,812,814],{"id":813},"persisting-the-data","Persisting the data",[211,816,817],{},"So - right now - the ContentView can take scanned data and can convert it to a Config option. We want to do three things:",[229,819,820,823,826],{},[232,821,822],{},"Add a save function",[232,824,825],{},"Add a load function",[232,827,828],{},"Move the decoder out of the view",[211,830,831],{},"For simplicity I'm going to add these as static methods on an extension on the Config object itself.",[242,833,835],{"id":834},"decoder","Decoder",[211,837,838],{},"First up we'll move the decode:",[253,840,842],{"className":255,"code":841,"language":257,"meta":258,"style":258},"extension Config {\n    static func decodeConfig(json: String) -> Config? {\n        if let data = json.data(using: .utf8) {\n            let decoder = JSONDecoder()\n\n            if let config = try? decoder.decode(Config.self, from: data) {\n                return config\n            } else {\n                print(\"Could not decode \\(json)\")\n            }\n        } else {\n            print(\"Could not convert to data \\(json)\")\n        }\n\n        return nil\n    }\n}\n",[260,843,844,854,887,919,934,938,972,980,990,1008,1012,1021,1037,1041,1045,1053,1057],{"__ignoreMap":258},[263,845,846,849,852],{"class":265,"line":266},[263,847,848],{"class":277},"extension",[263,850,851],{"class":395}," Config",[263,853,504],{"class":273},[263,855,856,859,862,865,867,870,872,874,877,880,882,885],{"class":265,"line":335},[263,857,858],{"class":277},"    static",[263,860,861],{"class":277}," func",[263,863,864],{"class":395}," decodeConfig",[263,866,320],{"class":273},[263,868,869],{"class":395},"json",[263,871,326],{"class":273},[263,873,407],{"class":269},[263,875,876],{"class":273},") ",[263,878,879],{"class":277},"->",[263,881,851],{"class":273},[263,883,884],{"class":277},"?",[263,886,504],{"class":273},[263,888,889,892,895,898,900,903,906,908,911,914,917],{"class":265,"line":351},[263,890,891],{"class":277},"        if",[263,893,894],{"class":277}," let",[263,896,897],{"class":273}," data ",[263,899,733],{"class":277},[263,901,902],{"class":273}," json.",[263,904,905],{"class":269},"data",[263,907,320],{"class":273},[263,909,910],{"class":269},"using",[263,912,913],{"class":273},": .",[263,915,916],{"class":269},"utf8",[263,918,410],{"class":273},[263,920,921,924,927,929,932],{"class":265,"line":365},[263,922,923],{"class":277},"            let",[263,925,926],{"class":273}," decoder ",[263,928,733],{"class":277},[263,930,931],{"class":269}," JSONDecoder",[263,933,519],{"class":273},[263,935,936],{"class":265,"line":371},[263,937,375],{"emptyLinePlaceholder":374},[263,939,940,943,945,948,950,953,956,959,962,964,966,969],{"class":265,"line":378},[263,941,942],{"class":277},"            if",[263,944,894],{"class":277},[263,946,947],{"class":273}," config ",[263,949,733],{"class":277},[263,951,952],{"class":277}," try?",[263,954,955],{"class":273}," decoder.",[263,957,958],{"class":269},"decode",[263,960,961],{"class":273},"(Config.",[263,963,359],{"class":277},[263,965,607],{"class":273},[263,967,968],{"class":269},"from",[263,970,971],{"class":273},": data) {\n",[263,973,974,977],{"class":265,"line":384},[263,975,976],{"class":277},"                return",[263,978,979],{"class":273}," config\n",[263,981,982,985,988],{"class":265,"line":389},[263,983,984],{"class":273},"            } ",[263,986,987],{"class":277},"else",[263,989,504],{"class":273},[263,991,992,995,997,1000,1003,1006],{"class":265,"line":413},[263,993,994],{"class":269},"                print",[263,996,320],{"class":273},[263,998,999],{"class":344},"\"Could not decode ",[263,1001,1002],{"class":344},"\\(json)",[263,1004,1005],{"class":344},"\"",[263,1007,550],{"class":273},[263,1009,1010],{"class":265,"line":419},[263,1011,788],{"class":273},[263,1013,1014,1017,1019],{"class":265,"line":553},[263,1015,1016],{"class":273},"        } ",[263,1018,987],{"class":277},[263,1020,504],{"class":273},[263,1022,1023,1026,1028,1031,1033,1035],{"class":265,"line":560},[263,1024,1025],{"class":269},"            print",[263,1027,320],{"class":273},[263,1029,1030],{"class":344},"\"Could not convert to data ",[263,1032,1002],{"class":344},[263,1034,1005],{"class":344},[263,1036,550],{"class":273},[263,1038,1039],{"class":265,"line":578},[263,1040,794],{"class":273},[263,1042,1043],{"class":265,"line":589},[263,1044,375],{"emptyLinePlaceholder":374},[263,1046,1047,1050],{"class":265,"line":619},[263,1048,1049],{"class":277},"        return",[263,1051,1052],{"class":269}," nil\n",[263,1054,1055],{"class":265,"line":629},[263,1056,800],{"class":273},[263,1058,1059],{"class":265,"line":643},[263,1060,422],{"class":273},[242,1062,1064],{"id":1063},"persisting","Persisting",[211,1066,1067,1068,1078],{},"Next step - save and load. We want to use the Keychain to save this information. The API for the keychain isn't the most swift like - but there are several nice libraries available to make it easier to use. We'll grab ",[1069,1070,1077],"a",{"href":1071,"rel":1072,"target":1076},"https:\u002F\u002Fgithub.com\u002Fevgenyneu\u002Fkeychain-swift.git",[1073,1074,1075],"nofollow","noopener","noreferer","_blank","KeychainSwift",".",[211,1080,1081,1082,1085],{},"So - add the package ",[1069,1083,1071],{"href":1071,"rel":1084,"target":1076},[1073,1074,1075]," via the Swift Package Manager.",[211,1087,1088],{},"We will need to save the data in some format. We could just store it as Data - but - if we actually encode it to json then in the load function we can simply reuse our decodeConfig to get it back as an object.",[253,1090,1092],{"className":255,"code":1091,"language":257,"meta":258,"style":258},"func save() {\n    let keychain = KeychainSwift()\n\n    let encoder = JSONEncoder()\n\n    if let data = try? encoder.encode(self) {\n        if let json = String(data: data, encoding: .utf8) {\n            keychain.set(json, forKey: \"AppConfig\")\n        }\n    }\n}\n\nstatic func loadConfig() -> Config? {\n    let keychain = KeychainSwift()\n\n    if let json = keychain.get(\"AppConfig\") {\n        return decodeConfig(json: json)\n    }\n\n    return nil\n}\n",[260,1093,1094,1104,1119,1123,1137,1141,1166,1196,1217,1221,1225,1229,1233,1254,1266,1270,1292,1305,1309,1313,1320],{"__ignoreMap":258},[263,1095,1096,1098,1101],{"class":265,"line":266},[263,1097,392],{"class":277},[263,1099,1100],{"class":395}," save",[263,1102,1103],{"class":273},"() {\n",[263,1105,1106,1109,1112,1114,1117],{"class":265,"line":335},[263,1107,1108],{"class":277},"    let",[263,1110,1111],{"class":273}," keychain ",[263,1113,733],{"class":277},[263,1115,1116],{"class":269}," KeychainSwift",[263,1118,519],{"class":273},[263,1120,1121],{"class":265,"line":351},[263,1122,375],{"emptyLinePlaceholder":374},[263,1124,1125,1127,1130,1132,1135],{"class":265,"line":365},[263,1126,1108],{"class":277},[263,1128,1129],{"class":273}," encoder ",[263,1131,733],{"class":277},[263,1133,1134],{"class":269}," JSONEncoder",[263,1136,519],{"class":273},[263,1138,1139],{"class":265,"line":371},[263,1140,375],{"emptyLinePlaceholder":374},[263,1142,1143,1146,1148,1150,1152,1154,1157,1160,1162,1164],{"class":265,"line":378},[263,1144,1145],{"class":277},"    if",[263,1147,894],{"class":277},[263,1149,897],{"class":273},[263,1151,733],{"class":277},[263,1153,952],{"class":277},[263,1155,1156],{"class":273}," encoder.",[263,1158,1159],{"class":269},"encode",[263,1161,320],{"class":273},[263,1163,359],{"class":269},[263,1165,410],{"class":273},[263,1167,1168,1170,1172,1175,1177,1180,1182,1184,1187,1190,1192,1194],{"class":265,"line":384},[263,1169,891],{"class":277},[263,1171,894],{"class":277},[263,1173,1174],{"class":273}," json ",[263,1176,733],{"class":277},[263,1178,1179],{"class":269}," String",[263,1181,320],{"class":273},[263,1183,905],{"class":269},[263,1185,1186],{"class":273},": data, ",[263,1188,1189],{"class":269},"encoding",[263,1191,913],{"class":273},[263,1193,916],{"class":269},[263,1195,410],{"class":273},[263,1197,1198,1201,1204,1207,1210,1212,1215],{"class":265,"line":389},[263,1199,1200],{"class":273},"            keychain.",[263,1202,1203],{"class":269},"set",[263,1205,1206],{"class":273},"(json, ",[263,1208,1209],{"class":269},"forKey",[263,1211,326],{"class":273},[263,1213,1214],{"class":344},"\"AppConfig\"",[263,1216,550],{"class":273},[263,1218,1219],{"class":265,"line":413},[263,1220,794],{"class":273},[263,1222,1223],{"class":265,"line":419},[263,1224,800],{"class":273},[263,1226,1227],{"class":265,"line":553},[263,1228,422],{"class":273},[263,1230,1231],{"class":265,"line":560},[263,1232,375],{"emptyLinePlaceholder":374},[263,1234,1235,1238,1240,1243,1246,1248,1250,1252],{"class":265,"line":578},[263,1236,1237],{"class":277},"static",[263,1239,861],{"class":277},[263,1241,1242],{"class":395}," loadConfig",[263,1244,1245],{"class":273},"() ",[263,1247,879],{"class":277},[263,1249,851],{"class":273},[263,1251,884],{"class":277},[263,1253,504],{"class":273},[263,1255,1256,1258,1260,1262,1264],{"class":265,"line":589},[263,1257,1108],{"class":277},[263,1259,1111],{"class":273},[263,1261,733],{"class":277},[263,1263,1116],{"class":269},[263,1265,519],{"class":273},[263,1267,1268],{"class":265,"line":619},[263,1269,375],{"emptyLinePlaceholder":374},[263,1271,1272,1274,1276,1278,1280,1283,1286,1288,1290],{"class":265,"line":629},[263,1273,1145],{"class":277},[263,1275,894],{"class":277},[263,1277,1174],{"class":273},[263,1279,733],{"class":277},[263,1281,1282],{"class":273}," keychain.",[263,1284,1285],{"class":269},"get",[263,1287,320],{"class":273},[263,1289,1214],{"class":344},[263,1291,410],{"class":273},[263,1293,1294,1296,1298,1300,1302],{"class":265,"line":643},[263,1295,1049],{"class":277},[263,1297,864],{"class":269},[263,1299,320],{"class":273},[263,1301,869],{"class":269},[263,1303,1304],{"class":273},": json)\n",[263,1306,1307],{"class":265,"line":649},[263,1308,800],{"class":273},[263,1310,1311],{"class":265,"line":659},[263,1312,375],{"emptyLinePlaceholder":374},[263,1314,1315,1318],{"class":265,"line":664},[263,1316,1317],{"class":277},"    return",[263,1319,1052],{"class":269},[263,1321,1322],{"class":265,"line":692},[263,1323,422],{"class":273},[211,1325,1326],{},"So now we cansave our new config on fetch and load it for use if present on start. For now - we'll handle this as a simple state variable in ContentView:",[242,1328,1330],{"id":1329},"fetch-and-save","Fetch and save",[253,1332,1334],{"className":255,"code":1333,"language":257,"meta":258,"style":258},"@State private var config : Config? = nil\n",[260,1335,1336],{"__ignoreMap":258},[263,1337,1338,1341,1344,1346,1349,1351,1354],{"class":265,"line":266},[263,1339,1340],{"class":277},"@State",[263,1342,1343],{"class":277}," private",[263,1345,472],{"class":277},[263,1347,1348],{"class":273}," config : Config",[263,1350,884],{"class":277},[263,1352,1353],{"class":277}," =",[263,1355,1052],{"class":269},[211,1357,1358],{},"Then our fetch function becomes:",[253,1360,1362],{"className":255,"code":1361,"language":257,"meta":258,"style":258},"func newScanData(_ code: String) {\n    if let config = Config.decodeConfig(json: code) {\n        config.save()\n        self.config = config\n    }\n}\n",[260,1363,1364,1380,1403,1413,1425,1429],{"__ignoreMap":258},[263,1365,1366,1368,1370,1372,1374,1376,1378],{"class":265,"line":266},[263,1367,392],{"class":277},[263,1369,396],{"class":395},[263,1371,320],{"class":273},[263,1373,401],{"class":395},[263,1375,404],{"class":273},[263,1377,407],{"class":269},[263,1379,410],{"class":273},[263,1381,1382,1384,1386,1388,1390,1393,1396,1398,1400],{"class":265,"line":335},[263,1383,1145],{"class":277},[263,1385,894],{"class":277},[263,1387,947],{"class":273},[263,1389,733],{"class":277},[263,1391,1392],{"class":273}," Config.",[263,1394,1395],{"class":269},"decodeConfig",[263,1397,320],{"class":273},[263,1399,869],{"class":269},[263,1401,1402],{"class":273},": code) {\n",[263,1404,1405,1408,1411],{"class":265,"line":351},[263,1406,1407],{"class":273},"        config.",[263,1409,1410],{"class":269},"save",[263,1412,519],{"class":273},[263,1414,1415,1418,1421,1423],{"class":265,"line":365},[263,1416,1417],{"class":269},"        self",[263,1419,1420],{"class":273},".config ",[263,1422,733],{"class":277},[263,1424,979],{"class":273},[263,1426,1427],{"class":265,"line":371},[263,1428,800],{"class":273},[263,1430,1431],{"class":265,"line":378},[263,1432,422],{"class":273},[242,1434,1436],{"id":1435},"load-and-display","Load and display",[211,1438,1439],{},"To make it easier to see what we have - we'll add a Text showing the account number if present - and we'll use onAppear to load from config if its there.",[211,1441,1442],{},"This becomes the current view:",[253,1444,1446],{"className":255,"code":1445,"language":257,"meta":258,"style":258},"var body: some View {\n    VStack {\n        Button(action: {\n            self.showingScanner = true\n        }) {\n            Image(systemName: \"qrcode\")\n                .resizable()\n                .frame(width: 32, height: 32)\n        }\n        .sheet(isPresented: $showingScanner) {\n            ScannerView(scannedData: Binding(\n            get: { \"\" },\n            set: self.newScanData\n            ))\n        }\n\n        if (self.config != nil) {\n            Text(config!.accountNr)\n        } else {\n            Text(\"You need to scan in a configuation\")\n        }\n    }\n    .onAppear {\n        self.config = Config.loadConfig()\n    }\n}\n",[260,1447,1448,1458,1465,1478,1491,1496,1512,1521,1545,1549,1565,1580,1591,1602,1607,1611,1615,1634,1648,1656,1667,1671,1675,1685,1700,1704],{"__ignoreMap":258},[263,1449,1450,1452,1454,1456],{"class":265,"line":266},[263,1451,278],{"class":277},[263,1453,490],{"class":273},[263,1455,493],{"class":277},[263,1457,496],{"class":273},[263,1459,1460,1463],{"class":265,"line":335},[263,1461,1462],{"class":269},"    VStack",[263,1464,504],{"class":273},[263,1466,1467,1470,1472,1475],{"class":265,"line":351},[263,1468,1469],{"class":269},"        Button",[263,1471,320],{"class":273},[263,1473,1474],{"class":269},"action",[263,1476,1477],{"class":273},": {\n",[263,1479,1480,1483,1486,1488],{"class":265,"line":365},[263,1481,1482],{"class":269},"            self",[263,1484,1485],{"class":273},".showingScanner ",[263,1487,733],{"class":277},[263,1489,1490],{"class":269}," true\n",[263,1492,1493],{"class":265,"line":371},[263,1494,1495],{"class":273},"        }) {\n",[263,1497,1498,1501,1503,1505,1507,1510],{"class":265,"line":378},[263,1499,1500],{"class":269},"            Image",[263,1502,320],{"class":273},[263,1504,568],{"class":269},[263,1506,326],{"class":273},[263,1508,1509],{"class":344},"\"qrcode\"",[263,1511,550],{"class":273},[263,1513,1514,1517,1519],{"class":265,"line":384},[263,1515,1516],{"class":273},"                .",[263,1518,584],{"class":269},[263,1520,519],{"class":273},[263,1522,1523,1525,1527,1529,1531,1533,1535,1537,1539,1541,1543],{"class":265,"line":389},[263,1524,1516],{"class":273},[263,1526,594],{"class":269},[263,1528,320],{"class":273},[263,1530,599],{"class":269},[263,1532,326],{"class":273},[263,1534,604],{"class":269},[263,1536,607],{"class":273},[263,1538,610],{"class":269},[263,1540,326],{"class":273},[263,1542,604],{"class":269},[263,1544,550],{"class":273},[263,1546,1547],{"class":265,"line":413},[263,1548,794],{"class":273},[263,1550,1551,1554,1557,1559,1562],{"class":265,"line":419},[263,1552,1553],{"class":273},"        .",[263,1555,1556],{"class":269},"sheet",[263,1558,320],{"class":273},[263,1560,1561],{"class":269},"isPresented",[263,1563,1564],{"class":273},": $showingScanner) {\n",[263,1566,1567,1570,1572,1574,1576,1578],{"class":265,"line":553},[263,1568,1569],{"class":269},"            ScannerView",[263,1571,320],{"class":273},[263,1573,323],{"class":269},[263,1575,326],{"class":273},[263,1577,329],{"class":269},[263,1579,332],{"class":273},[263,1581,1582,1585,1587,1589],{"class":265,"line":560},[263,1583,1584],{"class":269},"            get",[263,1586,341],{"class":273},[263,1588,345],{"class":344},[263,1590,348],{"class":273},[263,1592,1593,1596,1598,1600],{"class":265,"line":578},[263,1594,1595],{"class":269},"            set",[263,1597,326],{"class":273},[263,1599,359],{"class":269},[263,1601,362],{"class":273},[263,1603,1604],{"class":265,"line":589},[263,1605,1606],{"class":273},"            ))\n",[263,1608,1609],{"class":265,"line":619},[263,1610,794],{"class":273},[263,1612,1613],{"class":265,"line":629},[263,1614,375],{"emptyLinePlaceholder":374},[263,1616,1617,1619,1622,1624,1626,1629,1632],{"class":265,"line":643},[263,1618,891],{"class":277},[263,1620,1621],{"class":273}," (",[263,1623,359],{"class":269},[263,1625,1420],{"class":273},[263,1627,1628],{"class":277},"!=",[263,1630,1631],{"class":269}," nil",[263,1633,410],{"class":273},[263,1635,1636,1639,1642,1645],{"class":265,"line":649},[263,1637,1638],{"class":269},"            Text",[263,1640,1641],{"class":273},"(config",[263,1643,1644],{"class":277},"!",[263,1646,1647],{"class":273},".accountNr)\n",[263,1649,1650,1652,1654],{"class":265,"line":659},[263,1651,1016],{"class":273},[263,1653,987],{"class":277},[263,1655,504],{"class":273},[263,1657,1658,1660,1662,1665],{"class":265,"line":664},[263,1659,1638],{"class":269},[263,1661,320],{"class":273},[263,1663,1664],{"class":344},"\"You need to scan in a configuation\"",[263,1666,550],{"class":273},[263,1668,1669],{"class":265,"line":692},[263,1670,794],{"class":273},[263,1672,1673],{"class":265,"line":701},[263,1674,800],{"class":273},[263,1676,1677,1680,1683],{"class":265,"line":724},[263,1678,1679],{"class":273},"    .",[263,1681,1682],{"class":269},"onAppear",[263,1684,504],{"class":273},[263,1686,1687,1689,1691,1693,1695,1698],{"class":265,"line":739},[263,1688,1417],{"class":269},[263,1690,1420],{"class":273},[263,1692,733],{"class":277},[263,1694,1392],{"class":273},[263,1696,1697],{"class":269},"loadConfig",[263,1699,519],{"class":273},[263,1701,1702],{"class":265,"line":758},[263,1703,800],{"class":273},[263,1705,1706],{"class":265,"line":767},[263,1707,422],{"class":273},[211,1709,1710],{},"SwiftUI doesn't like things like \"if let x ...\" but will happily cope with a simple if\u002Felse with a check on nil.",[215,1712],{},[218,1714,1716],{"id":1715},"summary","Summary",[211,1718,1719],{},"So - we can now save the configuration to the device keychain on a scan and load it from the keychain at startup. The next step will be to protect the app with Face or Touch ID.",[215,1721],{},[211,1723,1724],{},[1069,1725,1728],{"href":1726,"rel":1727,"target":1076},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Flommepenger-swiftui",[1073,1074,1075],"GitHub Repository",[1730,1731,1732],"style",{},"html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":258,"searchDepth":335,"depth":335,"links":1734},[1735,1739,1745],{"id":220,"depth":335,"text":221,"children":1736},[1737,1738],{"id":244,"depth":351,"text":245},{"id":291,"depth":351,"text":292},{"id":813,"depth":335,"text":814,"children":1740},[1741,1742,1743,1744],{"id":834,"depth":351,"text":835},{"id":1063,"depth":351,"text":1064},{"id":1329,"depth":351,"text":1330},{"id":1435,"depth":351,"text":1436},{"id":1715,"depth":335,"text":1716},"2020-03-04 07:54 +0100",null,"md",{},"\u002F2020\u002F03\u002F04\u002Fview-refactor-and-save-to-keychain",{"title":206,"description":213},"Revisiting the Sbanken API with SwiftUI",{"loc":1750},"2020\u002F03\u002F04\u002Fview-refactor-and-save-to-keychain",[1756,257,1757,1758],"ios","swiftui","xcode","pKNsiMRKINerNECzHU24RwSLQZMqDzsQWgzv0iUd904",1775293007074]