Google Text-To-Speech in swift - swift

I am creating an app that has the text as input and must give out the speech as the output. I would like to send a function that I have created for fetching the text which will be the input and have the the text-to-speech return as the output. I have created a model for decoding the data generated from the Json data below for both input (JokesAPI), and output(ChatBotAPI). I have created a view controller that will return a speech for when the user taps a button. I am wondering, how can I implement this button for returning two separate functions when the button is tapped, both the text that is generated from the first API, and the text-to-speech that is generated from the google api ? Below is the functions that I have created for both getting data back from the APIs, and the button that I would like use for both functions
// the function for the Text that the google text-speech will use as an input
func fetchJokes()
// The function that grabs the data from google
import Foundation
class ChatBotAPI {
var session: URLSession?
var API_KEY = doSomethingDope
func fetchTextToSpeech(key: String, completion: #escaping ([Voice]) -> Void, error errorHandler: #escaping (String?) -> Void){
guard let url = URL(string: "https://cloud.google.com/text-to-speech/docs/reference/rest/?apix=true#service:-texttospeech.googleapis.com")
else {
errorHandler("Invalid URL endpoint")
return
}
guard let key = API_KEY else {
print("Can not generate these parameters for the API Key")
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
errorHandler(error.localizedDescription)
return
}
guard let response = response
else {
errorHandler("Could not generate a response")
return
}
struct ChatBotResponse: Decodable {
let data: [Voice]
}
guard let data = data else {
errorHandler("No data")
return
}
do {
let chatBotResponse = try JSONDecoder().decode(ChatBotResponse.self, from: data)
completion(chatBotResponse.data)
} catch {
print("Error decoding chat bot response:", error)
errorHandler("Something went wrong")
}
}
task.resume()
}
}
//Model
// MARK: - Voice
struct Voice: Decodable {
let audioConfig: AudioConfig
let input: Input
let voice: VoiceClass
}
// MARK: - AudioConfig
struct AudioConfig: Decodable {
let audioEncoding: String
let effectsProfileID: [String]
let pitch, speakingRate: Double
enum CodingKeys: String, CodingKey {
case audioEncoding
case effectsProfileID = "effectsProfileId"
case pitch, speakingRate
}
}
// MARK: - Input
struct Input: Decodable {
let text: String
}
// MARK: - VoiceClass
struct VoiceClass: Decodable {
let languageCode, name: String
}
// ViewController with button
#IBAction func didPressBtn() {
// MARK: Actions for both fetching a joke from the API, & Text-Speech using AI Voice
}

func fetchJokes()
{
//whenever you get the response of jokes, call the function fetchTextToSpeech by passing jokes as string
let joke = "This is a joke. This is only a joke."
fetchTextToSpeech(strJokes: joke)
}
func fetchTextToSpeech(strJokes : String)
{
var speechSynthesizer = AVSpeechSynthesizer()
var speechUtterance: AVSpeechUtterance = AVSpeechUtterance(string: strJokes)
speechUtterance.rate = AVSpeechUtteranceMaximumSpeechRate / 3.0
speechUtterance.voice = AVSpeechSynthesisVoice(language: "en-US")
speechSynthesizer.speak(speechUtterance)
}
#IBAction func didPressBtn() {
fetchJokes()
}

Related

How to bind data to ViewModel for showing it on UI in MVVM?

In my app I am using MVVM pattern.
Below is my Model.
struct NewsModel: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title: String
let articleDescription: String?
let url: String
let urlToImage: String?
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case source, author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
Below is my ViewModel. Which is used for show the data from API.
struct NewsArticleViewModel {
let article: Article
var title:String {
return self.article.title
}
var publication:String {
return self.article.articleDescription!
}
var imageURL:String {
return self.article.urlToImage!
}
}
Below is my API request class.
class Webservice {
func getTopNews(completion: #escaping (([NewsModel]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let news = try? JSONDecoder().decode([NewsModel].self, from: data)
DispatchQueue.main.async {
completion(news)
}
}.resume()
}
}
After receiving response from my API I want to show it on screen. For this I added below ViewModel.
class NewsListViewModel: ObservableObject {
#Published var news: [NewsArticleViewModel] = [NewsArticleViewModel]()
func load() {
fetchNews()
}
private func fetchNews() {
Webservice().getTopNews {
news in
if let news = news {
//How to bind this data to NewsArticleViewModel and show it on UI?
}
}
}
}
Please let me know. What I have to write there for showing it on UI.
According to the documentation of newsapi.org your request will return one NewsModel object not an array. So change your Webservice class to:
class Webservice {
//Change the completion handler to return an array of Article
func getTopNews(completion: #escaping (([Article]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
// decode to a single NewsModel object instead of an array
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let news = try? decoder.decode(NewsModel.self, from: data)
DispatchQueue.main.async {
// completion with an optional array of Article
completion(news?.articles)
}
}.resume()
}
}
You would need to map those received values to NewsArticleViewModel types. For example:
Webservice().getTopNews { articles in
if let articles = articles {
self.news = articles.map{NewsArticleViewModel(article: $0)}
}
}
And remove let news: NewsModel from the NewsArticleViewModel struct as it is not needed.
Edit:
It seems:
let publishedAt: Date
is throwing an error. Jsondecoder fails to interpret the string to a date. Change your Webservice. I´ve updated it in my answer.
You could remove the legacy MVVM pattern and do it in proper SwiftUI like this:
struct ContentView: View {
#State private var articles = [Article]()
var body: some View {
NavigationView {
List(articles) { article in
Text(article.title)
}
.navigationTitle("Articles")
}
.task {
do {
let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb")!
let (data, _) = try await URLSession.shared.data(from: url)
articles = try JSONDecoder().decode([Article].self, from: data)
} catch {
articles = []
}
}
}
}

Unable to decode a JSON file

I am learning Swift/SwiftUI and I faced a problem. The Xcode's writing this:
"Expected to decode Dictionary<String, CurrencyData> but found an array instead."
Here's a code:
import Foundation
import SwiftUI
struct CurrencyData: Codable {
let r030: Int
let txt: String
let rate: Double
let cc: String
let exchangedate: String
}
typealias Currency = [String: CurrencyData]
import SwiftUI
class API: ObservableObject {
#Published var currencyCode: [String] = []
#Published var priceRate: [Double] = []
#Published var exchangeDate: [String] = []
init() {
fetchdata { (currency) in
switch currency {
case .success(let currency):
currency.forEach { (c) in
DispatchQueue.main.async {
self.currencyCode.append(c.value.cc)
self.priceRate.append(c.value.rate)
self.exchangeDate.append(c.value.exchangedate)
}
}
case(.failure(let error)):
print("Unable to featch the currencies data", error)
}
}
}
func fetchdata(completion: #escaping (Result<Currency,Error>) -> ()) {
guard let url = URL(string: "https://bank.gov.ua/NBUStatService/v1/statdirectory/exchange?json") else { return }
URLSession.shared.dataTask(with: url) { data, responce, error in
if let error = error {
completion(.failure(error))
return
}
guard let safeData = data else { return }
do {
let currency = try JSONDecoder().decode(Currency.self, from: safeData)
completion(.success(currency))
}
catch {
completion(.failure(error))
}
}
.resume()
}
}
Change the type to correctly decode what you receive ( an array obviously )
This should work, but we can't answer for sure since we have no idea of the data. An attached json sample could help.
typealias Currency = [CurrencyData]
Change the exchangeDate in your CurrencyData model to exchangedate which need to match exactly the same as api response. It was case sensitive.

Why am I not able to recieve any data from the open weather api?

I am using swift and I want to receive data of the temp and humidity but all I am receiving is nil. I have two objects temp and humdity in another swift file. What am I doing wrong in my code? Not sure what I am missing.
struct Weather: Codable {
var temp: Double?
var humidity: Int?
var name : String?
}
struct WeatherMain: Codable {
let main: Weather
}
ViewController
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var latitudeValue = Double()
var longitudeValue = Double()
#IBOutlet weak var humidityLabel: UILabel!
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
print("locations = \(locValue.latitude) \(locValue.longitude)")
latitudeValue = locValue.latitude
longitudeValue = locValue.longitude
}
func retrieve() {
fetchWeather(lat: latitudeValue, lon: longitudeValue)
{ (response , error ) in
for res in response! {
print("Humid value is \(res.humidity ?? 0)")
}
}
}
#IBAction func showData(_ sender: Any) {
retrieve()
}
}
extension ViewController {
func fetchWeather(lat: Double, //Required
lon: Double,
completionHandler: #escaping ([Weather]?, Error?) -> Void) {
// MARK: Retrieve
let apikey = "45345345345343454Fake API"
/// create URL
let baseURL = "https://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=\(apikey)"
let url = URL(string: baseURL)
print("this is the url for weather : \(url!)")
/// Creating request
var request = URLRequest(url: url!)
request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let err = error {
print(err.localizedDescription)
}
do {
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let resp = json as? NSDictionary else { return }
/// weather
guard let weatherDic = resp.value(forKey: "weather") as? [NSDictionary] else { return }
let weatherData = try? JSONDecoder().decode(WeatherMain.self, from: data!)
var weatherList: [Weather] = []
/// Accessing each weather
for weatherObject in weatherDic {
if let weatherData = weatherData {
var weather = weatherData.main
//print("This is the temp \(weather.temp!)")
//print("This is the humidity \(weather.humidity!)")
weather.temp = weatherObject.value(forKey: "temp") as? Double
weather.humidity = weatherObject.value(forKey: "humidity") as? Int
weather.name = weatherObject.value(forKey: "name") as? String
weatherList.append(weather)
}
}
completionHandler(weatherList, nil)
} catch {
print("Caught error")
completionHandler(nil, error)
}
}.resume()
}
}
Well, as I see there are few mistakes in your code.
You will never receive location updates due to that you haven't set CLLocationManagerDelegate, you haven't requested authorization to use location, you haven't asked location manager to start updating locations.
Your response parsing code doesn't seems to be correct. You need to learn how to use Codable to parse JSON responses.
I've modified a bit your code, so that it works. But prior copy-pasting please open your Info.plist and add the following keys and values for the keys. This is important step.
Privacy - Location Usage Description
Privacy - Location When In Use Usage Description
The next step is to create correct Response model. To simplify the process of creating Response models you can use a website https://quicktype.io
Here is what the website generated for that api response:
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let weatherResponse = try? newJSONDecoder().decode(WeatherResponse.self, from: jsonData)
import Foundation
// MARK: - WeatherResponse
struct WeatherResponse: Codable {
let coord: Coord?
let weather: [Weather]?
let base: String?
let main: Main?
let visibility: Int?
let wind: Wind?
let clouds: Clouds?
let dt: Int?
let sys: Sys?
let timezone, id: Int?
let name: String?
let cod: Int?
}
// MARK: - Clouds
struct Clouds: Codable {
let all: Int?
}
// MARK: - Coord
struct Coord: Codable {
let lon, lat: Double?
}
// MARK: - Main
struct Main: Codable {
let temp, feelsLike, tempMin, tempMax: Double?
let pressure, humidity: Int?
enum CodingKeys: String, CodingKey {
case temp
case feelsLike = "feels_like"
case tempMin = "temp_min"
case tempMax = "temp_max"
case pressure, humidity
}
}
// MARK: - Sys
struct Sys: Codable {
let type, id: Int?
let country: String?
let sunrise, sunset: Int?
}
// MARK: - Weather
struct Weather: Codable {
let id: Int?
let main, weatherDescription, icon: String?
enum CodingKeys: String, CodingKey {
case id, main
case weatherDescription = "description"
case icon
}
}
// MARK: - Wind
struct Wind: Codable {
let speed, deg: Int?
}
And finally your updated ViewController
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var humidityLabel: UILabel!
let locationManager = CLLocationManager()
var location: CLLocation?
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self // set your CLLocationManagerDelegate to your ViewController instance
checkAuthorizationStatus() // Check current authorization status
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
location = locations.first
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkAuthorizationStatus()
}
private func checkAuthorizationStatus() {
var authorizationStatus: CLAuthorizationStatus!
if #available(iOS 14.0, *) {
authorizationStatus = locationManager.authorizationStatus
} else {
authorizationStatus = CLLocationManager.authorizationStatus()
}
switch authorizationStatus ?? .notDetermined {
case .authorizedAlways, .authorizedWhenInUse: // If authorized
locationManager.startUpdatingLocation() // request updating location
case CLAuthorizationStatus.denied, CLAuthorizationStatus.restricted: // if denied we are not able to receive location updates
print("Application doesn't have access to location.")
case CLAuthorizationStatus.notDetermined: // if not determined we can request authorization
locationManager.requestWhenInUseAuthorization() // request authorization
#unknown default:
print("Unknown authorization status")
}
}
func retrieve() {
guard let location = location else {
print("Location is nil.")
return
}
fetchWeather(forLocation: location) { [weak self] (result) in
switch result {
case .success(let weatherResponse):
print("WeatherResponse: \(weatherResponse)")
if let humidity = weatherResponse?.main?.humidity {
self?.humidityLabel.text = String(humidity)
}
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
}
#IBAction func showData(_ sender: Any) {
retrieve()
}
}
extension ViewController {
func fetchWeather(forLocation location: CLLocation, completion: #escaping (Result<WeatherResponse?, Error>) -> Void) {
let apikey = "YOUR_API_KEY_HERE"
let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&appid=\(apikey)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
print("Request: \(url.absoluteString)")
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
completion(.failure(error))
}
}
guard let data = data else {
print("No response data.")
DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
completion(.success(nil))
}
return
}
if let responseString = String(data: data, encoding: .utf8) { // Move completion to the main queue, so that you can work with UI stuff
print("Response: \(responseString)")
}
do {
let response = try JSONDecoder().decode(WeatherResponse.self, from: data) // Here is the magic of Codable, Just simply set expected Codable type
DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
completion(.success(response))
}
} catch {
DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
completion(.failure(error))
}
}
}.resume()
}
}
Happy coding, don't give up)

HTTP post request and save response in app

I'm totally new to swift and iOS programming so I'm a little lost on how to do this and even in what files I should be doing this too.
I'm trying to do a http post request to get calendar events and save them in the app to later use and display.
I made a model class with this code.
import UIKit
class Event {
var id: Int
var init_date: String
var end_date: String
var title: String
var description: String
var color_code: String
var all_day: Int
init?(id: Int, init_date: String, end_date: String, title: String, description: String, color_code: String, all_day: Int) {
//Initialization should fail if these are false
if id < 0 || init_date.isEmpty || end_date.isEmpty || title.isEmpty {
return nil
}
//Initialize stored properties
self.id = id
self.init_date = init_date
self.end_date = end_date
self.title = title
self.description = description
self.color_code = color_code
self.all_day = all_day
}
}
But now I don't know what the next step would be. I need this to be downloaded immediately once the app is opened for the first time and not when it's not being opened for the first time. Do I create a new method in the ViewController.swift for the download?
Right now I haven't added anything to the ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
What should I do next?
At this point you need to create a function that handles the POST request you are making.
Once completed, place this function inside your appDelegate main function didFinishLaunchingWithOptions. This is the function that executes on appStart
On a successful function call save the data (presumably json) into a Global Variable or whatever you need for you app.
TIP:
On you class
class Event: Codable {
}
make sure to add Codable like above
Below is an example of what your post request will look like
func myPostRequest(completionHandler: #escaping (Bool?, String?) -> Void){
guard let url = URL(string:"") else { return }
let parameters = ["": ""]
var request: URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("", forHTTPHeaderField: "Authorization")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
error == nil
else {
print(error as Any)
return
}
if let httpResponse = response as? HTTPURLResponse {
if (httpResponse.statusCode == 200) {
if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
//print("^^^^^^^^^^^^^^",json)
for x in json ?? [] {
//here is where you will parse your data from the post request
}
completionHandler(true, nil)
return
}
} else {
completionHandler(false, "No Response From Server")
print("Failure response: STATUS CODE != 200")
}
} else {
completionHandler(false, "Database Connection Error")
print("Error \(error!)")
}
}
task.resume()
} catch let error {
completionHandler(false, "failure")
print("POSTERROR: \(error.localizedDescription)")
}
}
I use Alamofire, you can add it to your project via:
Pods
Swift Package Manager
When you add the framework you can use it:
import Alamofire
Then you need to make your class with the protocol Codable to pass the data to your class.
class Event: Codable { }
Then you need to call the url and store the response in a variable:
override func viewDidAppear(_ animated: Bool) {
AF.request("your API rest url").responseData { (resData) in
guard let data = resData.data else { return }//Check if the data is valid
do {
let decoder = JSONDecoder()//Initialize a Json decoder variable
let decodedData = try decoder.decode(Event.self, from: data)//Decode the response data to your decodable class
//Print the values
print(decodedData.headers)
print(decodedData.id)
print(decodedData.init_date)
print(decodedData.end_date)
} catch {
print(error.localizedDescription)
}
}
}

How do I put a picture from a JSON URL in the background of a UIViewController?

I have a ViewController and in these I would like to set an image as a background image via JSON URL.
I also wanted to present a picture of this JSON request with my JSON request which I already used for my News UITableViewController, but that did not work
Here is my code:
var courses = [Course]()
var activityIndicatorView: UIActivityIndicatorView!
var rows: [String]?
let dispatchQueue = DispatchQueue(label: "Example Queue")
let borderwidth = 10
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true
fetchJSON()
}
// MARK: - Courses
struct Course: Codable {
let guid: GUID
let links: Links
enum CodingKeys: String, CodingKey {
case guid
case links = "_links"
}
}
// MARK: - GUID
struct GUID: Codable {
let rendered: String
}
// MARK: - Links
struct Links: Codable {
}
fileprivate func fetchJSON() {
let urlString = "EXAMPLE_URL"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
self.courses = try JSONDecoder().decode([Course].self, from: data)
self.tableView.reloadData()
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}
I would like that the image occupies the entire ViewController and in the middle of a transparent box with welcome text
However, I do not know how to load a picture from a post.