Swift UIKit天气APP API通信Bug:非拉丁字符请求失败排查
Swift UIKit天气应用非拉丁字符API请求问题
问题现象
- 含非拉丁字符的城市名(如"Eskişehir")请求失败,转成纯拉丁字符("Eskisehir")后请求正常
- 直接在Chrome地址栏粘贴带非拉丁字符的URL能返回有效数据,但Xcode模拟器中执行相同请求无结果
相关代码
//=====================View controller ========== func textFieldDidEndEditing(_ textField: UITextField) { if let city = searchText.text{ weatherManager.fetchWeather(city) } searchText.text = "" } func weatherDataFetched(_ weatherDataModel: WeatherDataModel){ tempLabel.text = weatherDataModel.tempString cityLabel.text = weatherDataModel.name weatherImage.image = UIImage(systemName: weatherDataModel.weatherStatus) } //========================== Weather manager side===================== struct WeatherManager{ static let apiKey = "bla bla bla" static let units = "metric" var url = "https://api.openweathermap.org/data/2.5/weather?units=\(units)&appid=\(apiKey)" var delegate : WeatherManagerDelegation? func fetchWeather(_ cityName:String){ let fullUrl = "\(url)&q=\(cityName)" sendRequest(fullUrl) } func sendRequest(_ fullUrl: String){ if let url = URL(string: fullUrl){ let urlSession = URLSession(configuration: .default) let dataTask = urlSession.dataTask(with: url) { data, response, error in if error != nil{ print("DataTask Error!") return } if let checkedData = data{ if let weatherDataModel = self.parseJSON(weatherData: checkedData){ DispatchQueue.main.async { self.delegate?.weatherDataFetched(weatherDataModel) } } } } dataTask.resume() } } func parseJSON(weatherData: Data) -> WeatherDataModel?{ let decoder = JSONDecoder() do{ let decodedModel = try decoder.decode(WeatherData.self, from: weatherData) let id = decodedModel.weather[0].id let temp = decodedModel.main.temp let name = decodedModel.name let weatherDataModel = WeatherDataModel(id: id, name: name, temp: temp) print(temp) return weatherDataModel } catch{ print("Json parsing error") return nil } } } //===============Weather Data ================ struct WeatherData : Decodable{ let name: String let main: Main let weather: [Weather] } struct Main : Decodable{ let temp: Double } struct Weather : Decodable{ let id: Int } //============== Weather Data Model ================= struct WeatherDataModel{ let id: Int let name: String let temp: Double var tempString: String{ return String(format: "%.1f", temp) } var weatherStatus: String{ switch id{ case 200...299: return Constants.bolt case 300...399: return Constants.drizzle case 500...599: return Constants.rain case 600...699: return Constants.snow case 700...799: return Constants.fog case 800: return Constants.sun case 801...900: return Constants.bolt default: return Constants.cloud } } init(id: Int, name: String, temp: Double) { self.id = id self.name = name self.temp = temp } }
问题原因
核心原因是URL未进行正确的百分号编码:
- 浏览器会自动对URL中的非ASCII字符(比如"ş")进行百分号编码(转成
%C5%9F),所以直接在地址栏能正常请求 - 但Swift中直接拼接含非拉丁字符的字符串生成URL时,
URL(string:)会返回nil,导致后续请求根本没执行(代码里sendRequest方法中如果URL(string: fullUrl)返回nil,就不会创建dataTask,自然无结果)
解决方案
修改fetchWeather方法,对城市名称进行URL编码:
func fetchWeather(_ cityName: String) { // 对城市名称进行URL编码,指定编码格式为UTF-8 guard let encodedCityName = cityName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { print("城市名称编码失败") return } let fullUrl = "\(url)&q=\(encodedCityName)" sendRequest(fullUrl) }
addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)会把所有不符合URL查询参数规范的字符(包括非拉丁字符)转换成百分号编码格式,确保URL合法- 编码后的URL会和浏览器自动处理的格式一致,API就能正常识别请求
后续问题解答
1. 支持用户用母语搜索是否有难度?
难度不大,只要做好两步:
- 确保城市名称的URL编码正确(上面的解决方案已经覆盖)
- 确认OpenWeatherMap API支持对应语言的城市名称搜索:OpenWeatherMap本身支持多语言城市名称查询,只要编码正确,大部分母语城市名(含非拉丁字符)都能被识别
2. 如何限制用户仅使用拉丁字符?
可以通过文本输入框的UITextFieldDelegate方法限制输入内容:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // 定义允许的拉丁字符集合:字母、空格、连字符等(可根据需求调整) let allowedCharacters = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ -") let characterSet = CharacterSet(charactersIn: string) return allowedCharacters.isSuperset(of: characterSet) }
- 实现这个方法后,用户输入非拉丁字符时会被拦截,无法输入到文本框中
- 也可以配合输入验证,在用户提交时提示只能输入拉丁字符
内容的提问来源于stack exchange,提问作者Anıl Karadağ
相关产品推荐
相关产品推荐

