How to get the value of searchBar.text of one VC and use it in another file of the project in swift? - swift

I've been looking for the answer everywhere and could't find any.. Is there any way I can access the value of searchBar.text in another file? I have the delegate set in my SearchVC but I also have a custom tableView cell in another file.
I need the value of the SearchBar of my SearchVC to use in FirstDefinitionVC for decoding the word from the searchBar and use it for finding the audio URL.
All works fine while I call the function inside the searchBarSearchButtonClicked method but I can find no way to pass that String into FirstDefintionVC.
The relevant searchVC code :
var word = ""
`
extension SearchVC: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
// { (data: [WordData], [Definitions])
word = searchBar.text!
wordManager.performRequest(word: word) { data in
self.wordData = data
self.searchButtonPressed = true
// print(data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
fetchAudio(word: word) { data in //this one works fine
DispatchQueue.main.async {
self.wordData = data
}
}
}
func fetchAudio(word: String, comp: #escaping ([WordData])-> Void) {
let wordURL = "https://api.dictionaryapi.dev/api/v2/entries/en/"
let urlString = "\(wordURL)\(word)"
if let url = URL(string: urlString) {
let dataTask = URLSession.shared.dataTask(with: url, completionHandler: {
(data,response,error) in
guard let data = data, error == nil else {
print("Error occured while accessing data with URL")
return
}
do {
let decoded = try JSONDecoder().decode([WordData].self, from: data)
comp(decoded)
if let sound = decoded[0].phonetics[0].audio,
let sound2 = decoded[0].phonetics[1].audio {
print("sound = \(sound)")
let nonEmpty = (sound != "") ? sound : sound2 //write switch cases or another ternary with more urls to choose from if both are empty
self.audioUrl = URL(string: nonEmpty)
// url = URL(string: sound2)
do {
try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
self.player = AVPlayer(url: self.audioUrl!)
guard let player = self.player else { return }
player.play()
} catch let error {
print(error.localizedDescription)
}
}
//comp(decoded, entries.self)
} catch {
print("Error occured while decoding JSON into Swift structure \(error)")
}
})
dataTask.resume()
}
}
I need to call the searchBar.text value in another file inside this IBAction of class FirstDefinitionVC:
`
#IBAction func pronunciationButton(_ sender: UIButton) {
searchVC.fetchAudio(word: searchVC.word) { data in
self.wordData = data
}
}
This was one of my approaches to this, I tried to create a global model Word with an initializer also and it didn't work. Is there any way around it?

Related

Swift after json parsing variables are assigned to their initial values

I'm new to swift i am sorry if this is a stupid question
I am trying to expand my knowledge in macOS development and i am trying out new things
i am parsing a json file from an url
it works fine in the do{}catch{} brackets however, i want to use what i get from the json data in other parts of the program.
i created some variables to store the values.
However, they go back to their initial value once the do{}catch{} execution is done
how can i store the values I got
#IBAction func buttonPressed(_ sender: Any) {
var summonerNameGlobal: String = ""
var summonerIdGlobal: String = ""
var summonerPuuidGlobal: String = ""
var summonerAccountIdGlobal: String = ""
let jsonString = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/john?api_key=\(apiKey)"
guard let url = URL(string: jsonString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
DispatchQueue.main.async {
do {
let summoner = try JSONDecoder().decode(SummonerInfo.self, from: data)
self.summonerIdLabel.stringValue = summoner.id
summonerNameGlobal = summoner.name
summonerIdGlobal = summoner.id
summonerAccountIdGlobal = summoner.accountId
summonerPuuidGlobal = summoner.puuid
} catch {
print(error)
}
}
}.resume()
print(summonerNameGlobal)
print(summonerPuuidGlobal)
print(summonerIdGlobal)
print(summonerAccountIdGlobal)
}
They are not going to default again but you are checking them before they are being set ... because async function take some time to get response from server but your print statements run immediately
What you can do is to check values once they are set
func callApi(completion: #escaping (SummonerInfo?)->Void){
let jsonString = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/john?api_key=\(apiKey)"
guard let url = URL(string: jsonString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
DispatchQueue.main.async {
do {
let summoner = try JSONDecoder().decode(SummonerInfo.self, from: data)
completion(summoner)
} catch {
completion(nil)
print(error)
}
}
}.resume()
}
#IBAction func buttonPressed(_ sender: Any) {
callApi { [weak self] info in
if let getInfo = info {
print(getInfo.name)
print(getInfo.id)
print(getInfo.accountId)
print(getInfo.puuid)
} else {
print("data is nil")
}
}
}

Converting XML respons with numbers in String to Int Swift

Ive got an answer from an XML API that comes back to me as a String. I need it to be an int so that i can add it to another value in laters on. Ive tried to unwrap it and read it as an int but it didnt work. Ive also tried trimming blank spaces and then unwrap it but that didnt work either.
If i set the leading let value: Int it will give me an error saying that the value is not in the correct format.
What i have so far is this:
struct HydroData: Decodable {
let value: String
let textTranslationId: String?
let titleTranslationId: String?
let style: String?
}
struct HydroResult: Decodable {
let HydroData: [HydroData]
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
calcIndex()
let url = URL(string: "https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("No data")
return
}
do {
let result = try JSONDecoder().decode(HydroResult.self, from: data)
if let seDesc = result.HydroData.filter({ $0.titleTranslationId == "ProductionConsumption.HydroSEDesc" }).first {
let hydroValue = seDesc.value
print(seDesc.value)
} else {
print("Error: no value")
}
} catch {
print(error.localizedDescription)
}
}
task.resume() }
func calcIndex(){
let newHydro = hydroValue + 1000
print(newHydro)
}
}
You need to use initializer for Int that accepts String as parameter Int(). Also, I've fixed the issue you're gonna face when you try to use the Int(seDesc.value) because it contains a non-decimal-digit character. Here's the entire code:
class ViewController: UIViewController {
var hydroValue = 0
override func viewDidLoad() {
super.viewDidLoad()
calcIndex()
let url = URL(string: "https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("No data")
return
}
do {
let result = try JSONDecoder().decode(HydroResult.self, from: data)
if let seDesc = result.HydroData.filter({ $0.titleTranslationId == "ProductionConsumption.HydroSEDesc" }).first {
let value = seDesc.value.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
self.hydroValue = Int(value) ?? 0
print(value)
self.calcIndex()
} else {
print("Error: no value")
}
} catch {
print(error.localizedDescription)
}
}
task.resume()
}
func calcIndex(){
let newHydro = hydroValue + 1000
print(newHydro)
}
}

Return response as object in swift

I have a function that connects to an API to retrieve data. The API takes two parameters accessCode (provided by user in a text box) and then UDID (UDID of their device). I can parse the data from within the function, but only locally. I need to store the values that are returned but am unsure on how to return them properly. Essentially I need this to return the json object as a dictionary (I think...) so it can be parsed outside of the async task. I've read through the swift documentation and that's where I found out how to do the requests, but I can't find a way to store the returned values in memory for access outside of the function.
func getResponse(accessCode:String, UDID:String, _ completion: #escaping (NSDictionary) -> ()) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult as? NSDictionary
print(results)
completion(results!)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
First of all don't use NSDictionary in Swift, use native [String:Any] and declare the type as optional to return nil if an error occurs.
And never use .mutableContainers in Swift, the option is useless.
func getResponse(accessCode:String, UDID:String, completion: #escaping ([String:Any]?) -> Void)) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error else {
print(error)
completion(nil)
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
print(jsonResult)
completion(jsonResult)
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
task.resume()
}
Your mistake is that you don't consider the closure, you have to execute the entire code inside the completion handler
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { [weak self] result in
if let accessCodeFound = result?["Found"] as? Bool {
print("Value of Found during function:")
//If access code is valid, go to License view
print(accessCodeFound)
if accessCodeFound {
//Load License View
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self?.show(licenseController, sender: self)
}
}
}
}
}
Your completion closure should handle the obtained data. You would call the function like this:
getResponse(accessCode: "code", UDID: "udid", completion: { result in
// Do whatever you need to do with the dictionary result
}
Also, I'd recommend you to change your NSDictionary with a swift Dictionary.
This is what the API returns as a response
{
AccessCode = 00000000;
Client = "0000 - My Company Name";
EmailAddress = "brandon#brandonthomas.me";
FirstName = Brandon;
Found = 1;
LastName = Thomas;
Status = A;
UDIDregistered = 1;
}
And this is what calls the function. I am calling at after clicking a button after an access code is being entered in a text field.
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
var accessCodeFound: Bool? = nil
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { result in
accessCodeFound = result["Found"] as! Bool
print("Value of Found during function:")
print(accessCodeFound)
//accessCodeFound = true
}
//If access code is valid, go to License view
print("Value of Found after function:")
print(accessCodeFound)
//accessCodeFound = nil ???
//it seems the value is getting reset after the function completes
if accessCodeFound == true{
//Load License View
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self.show(licenseController, sender: Any?.self)
}
}

Is there a way to use my array of type Music, in another scope?

I'm attempting to print/dump and array of type Music outside of a function it's created in. I can successfully dump the musicItems array inside of the getMusicData function but when I set the musicItems array outside of the scope, it won't print anything. What am I doing wrong with the scope here? I have a feeling it's super simple but I just can't figure it out. Thanks in advance for taking the time to read this.
edit: It's giving me "0 elements" in the console when I attempt to dump the musicItems array in the ViewController class. Well, the function is in the same class as well so I guess I don't know what to call the first array. The parent array?
struct MusicResults: Decodable {
let results: [Music]?
}
struct Music: Decodable {
let trackName: String?
let collectionName: String?
let artworkUrl30: String?
}
class ViewController: UITableViewController, UISearchBarDelegate {
var musicItems: [Music] = []
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData()
dump(musicItems)
}
Here is the function.
func getMusicData() {
var musicItems: [Music] = []
guard let searchTerm = searchString else {return}
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
// print(results.trackName!)
musicItems.append(results)
}
//dump(musicItems)
self.musicItems = musicItems
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
Fixed Code
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData {
music in
dump(music)
}
function:
func getMusicData(completionHandler: #escaping (_ music: [Music]) -> ()) {
...
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
musicItems.append(results)
}
completionHandler(musicItems)
...
Your 'getMusicData' function is asynchronous which means that when it executes, it queues data task in a background queue and proceeds the execution and since there are no more institutions it simply returns control to its calling site - 'musicButton()' action, which in its turn executes the next instruction - prints the 'musicItems' array which might (and most likely, is) still not populated as the network call haven’t yet completed. One of the options that you have here is to pass a completion block to your 'getMusicData' function, that runs it after data task gets the results.
Another option is to use Property Observers
var musicItems: [Music] = [] {
didSet {
dump(self.musicItems)
/// This is where I would do the...
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
and then
func getMusicData() {
guard let searchTerm = searchString else { print("no search"); return }
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { print("url error"); return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { print(err ?? "unknown"); return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
if let results = music.results {
self.musicItems.append(contentsOf: results)
}
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}

WKWebView Screenshots

I am trying to capture the image that the webview is displaying to the user, so I can some color analysis of the web page. When I try to get the image from it's parent, I am basically getting a white box, even though the page has rendered:
func makeImageSnapshot()-> (NSImage)
{
let imgSize = self.view.bounds.size
let bir = self.viewbitmapImageRepForCachingDisplayInRect(self.webView!.view.bounds)
bir.size = imgSize
self.webView.cacheDisplayInRect(self.view.bounds, toBitmapImageRep:bir)
let image = NSImage(size:imgSize)
image.addRepresentation(bir)
self.image = image
return image
}
func saveSnapshot()
{
let imgRep = self.image!.representations[0]
let data = imgRep.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: nil)
data.writeToFile("/tmp/file.png", atomically: false)
}
It looks to me like I can't get access to the properties of the actual view (in this case the bounds) inside of the webView. When I try to access it, the compiler barfs:
/Users/josh/Canary/MacOsCanary/canary/canary/Modules/Overview/Overview.swift:55:37: '(NSView!, stringForToolTip: NSToolTipTag, point: NSPoint, userData: UnsafePointer<()>) -> String!' does not have a member named 'bounds'
My guess is that this is happening due to the extensions approach used by OS X and iOS. Any ideas, or should I just go back to using the legacy WebView?
I realise the question was for Mac OS X, but I found this page whilst searching for an iOS solution. My answer below doesn't work on Mac OS X as the drawViewHierarchyInRect() API call is currently iOS only, but I put it here for reference for other iOS searchers.
This Stackoverflow answer solved it for me on iOS 8 with a WKWebView. That answer's sample code is in Objective-C but the Swift equivalent to go in a UIView sub-class or extension would be along the lines of the code below. The code ignores the return value of drawViewHierarchyInRect(), but you may want to pay attention to it.
func imageSnapshot() -> UIImage
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0);
self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true);
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
Swift 3
extension WKWebView {
func screenshot() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0);
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true);
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
}
Note: This solution only works on iOS.
Found myself in the same boat today but found a solution (by using private APIs).
If you're not targeting the App Store and generally are not afraid of using private APIs, here's a way to capture screenshots of WKWebView's on OS X:
https://github.com/lemonmojo/WKWebView-Screenshot
You will need to have access to a target writeable place - the snapshotURL ie.., such as the desktop, so we provide a handler for that:
func registerSnaphotsURL(_ sender: NSMenuItem, handler: #escaping (URL) -> Void) {
var targetURL : URL
// 1st around authenticate and cache sandbox data if needed
if isSandboxed, desktopData == nil {
targetURL =
UserSettings.SnapshotsURL.value.count == 0
? getDesktopDirectory()
: URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value, isDirectory: true)
let openPanel = NSOpenPanel()
openPanel.message = "Authorize access to "
openPanel.prompt = "Authorize"
openPanel.canChooseFiles = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = true
openPanel.directoryURL = targetURL
openPanel.begin() { (result) -> Void in
if (result == .OK) {
targetURL = openPanel.url!
// Since we do not have data, clear any bookmark
if self.storeBookmark(url: targetURL, options: self.rwOptions) {
self.desktopData = self.bookmarks[targetURL]
UserSettings.SnapshotsURL.value = targetURL.absoluteString
if !self.saveBookmarks() {
print("Yoink, unable to save snapshot bookmark")
}
self.desktopData = self.bookmarks[targetURL]
handler(targetURL)
}
}
else
{
return
}
}
}
else
{
targetURL =
UserSettings.SnapshotsURL.value.count == 0
? getDesktopDirectory()
: URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value, isDirectory: true)
handler(targetURL)
}
}
we wanted to allow single (view controller) and all current views (app delegate) so two actions in their respective files, both making use of the register handler.
App Delegate
#objc #IBAction func snapshotAllPress(_ sender: NSMenuItem) {
registerSnaphotsURL(sender) { (snapshotURL) in
// If we have a return object just call them, else notify all
if let wvc : WebViewController = sender.representedObject as? WebViewController {
sender.representedObject = snapshotURL
wvc.snapshot(sender)
}
else
{
sender.representedObject = snapshotURL
let notif = Notification(name: Notification.Name(rawValue: "SnapshotAll"), object: sender)
NotificationCenter.default.post(notif)
}
}
}
View Controller
func viewDidLoad() {
NotificationCenter.default.addObserver(
self,
selector: #selector(WebViewController.snapshotAll(_:)),
name: NSNotification.Name(rawValue: "SnapshotAll"),
object: nil)
}
#objc func snapshotAll(_ note: Notification) {
snapshot(note.object as! NSMenuItem)
}
view singleton action
#objc #IBAction func snapshotPress(_ sender: NSMenuItem) {
guard let url = webView.url, url != webView.homeURL else { return }
guard let snapshotURL = sender.representedObject as? URL else {
// Dispatch to app delegate to handle a singleton
sender.representedObject = self
appDelegate.snapshotAllPress(sender)
return
}
sender.representedObject = snapshotURL
snapshot(sender)
}
the webView interaction to capture an image
#objc func snapshot(_ sender: NSMenuItem) {
guard let url = webView.url, url != webView.homeURL else { return }
guard var snapshotURL = sender.representedObject as? URL else { return }
// URL has only destination, so add name and extension
let filename = String(format: "%# Shapshot at %#",
(url.lastPathComponent as NSString).deletingPathExtension,
String.prettyStamp())
snapshotURL.appendPathComponent(filename)
snapshotURL = snapshotURL.appendingPathExtension("png")
webView.takeSnapshot(with: nil) { image, error in
if let image = image {
self.webImageView.image = image
DispatchQueue.main.async {
self.processSnapshotImage(image, to: snapshotURL)
}
}
else
{
self.userAlertMessage("Failed taking snapshot", info: error?.localizedDescription)
self.webImageView.image = nil
}
}
}
and the capture to the targeted area
func processSnapshotImage(_ image: NSImage, to snapshotURL: URL) {
guard let tiffData = image.tiffRepresentation else { NSSound(named: "Sosumi")?.play(); return }
let bitmapImageRep = NSBitmapImageRep(data: tiffData)
do
{
try bitmapImageRep?.representation(using: .png, properties: [:])?.write(to: snapshotURL)
// https://developer.apple.com/library/archive/qa/qa1913/_index.html
if let asset = NSDataAsset(name:"Grab") {
do {
// Use NSDataAsset's data property to access the audio file stored in Sound.
let player = try AVAudioPlayer(data:asset.data, fileTypeHint:"caf")
// Play the above sound file.
player.play()
} catch {
print("no sound for you")
}
}
if snapshotURL.hideFileExtensionInPath(), let name = snapshotURL.lastPathComponent.removingPercentEncoding {
print("snapshot => \(name)")
}
} catch let error {
appDelegate.userAlertMessage("Snapshot failed", info: error.localizedDescription)
}
}