Delegate not being called in Swift - swift3

I can't figure out why my Delegate is not being called.
Here is where I define the protocol and call the delegate:
protocol CommentRatingViewControllerDelegate: class {
func didCommentOrRatePost(updatedRating: Bool, addedComment:Bool)
}
class CommentRatingViewController: UIViewController, UITextViewDelegate {
weak var delegate:CommentRatingViewControllerDelegate?
...
#IBAction func saveRatingComment(_ sender: Any) {
var updatedRating = false
var addedComment = false
rating = ratingView.rating
if rating != 0.0 {
saveRating(articleID: post.articleID, userID: post.userID)
updatedRating = true
}
if commentsTextView.text != "" {
saveComment(articleID: post.articleID, userID: post.userID, comment: commentsTextView.text!)
addedComment = true
}
self.delegate?.didCommentOrRatePost(updatedRating: updatedRating, addedComment: addedComment)
close()
}
....
And here is where conform to the delegate protocol:
extension PostDetailViewController: CommentRatingViewControllerDelegate {
func didCommentOrRatePost(updatedRating: Bool, addedComment: Bool) {
if updatedRating == true || addedComment == true {
networkingState = .searching
if updatedRating {
getRating(articleID: post.articleID)
}
if addedComment {
post.numberOfComments += post.numberOfComments
}
networkingState = .finishedSearching
tableView.reloadData()
}
}
}

When you conform to a protocol, if you want to call delegate methods, it is not enough to make your class conform to the protocol, you also need to set the delegate to self inside the class.
class CommentRatingViewController: UIViewController, UITextViewDelegate {
override func viewDidLoad() {
self.delegate = self
}
}

Related

Swift UI App crash during the run time with main app

I am trying to call model form #main App where the model has the dependency on a repository with init function. The repository has the URLSession and Baseurl properties . I have passed the required property on both approach ..
Here is approach I have tried based on Xcode suggestions ..
#main
struct HomwWorkWithSwiftUIApp: App {
#StateObject var model = FruitsModel(fruitRepository: FruitsRepository.self as! FruitsRepository)
var body: some Scene {
WindowGroup {
ContentView().environmentObject(model)
}
}
}
As a result as was crashed at run time with error Thread 1: signal SIGABRT
The second approach is passing the require parameters like this ..
#main
struct HomwWorkWithSwiftUIApp: App {
#StateObject var model = FruitsModel(fruitRepository: RealFruitsRepository(session: URLSession, baseURL: EndPoint.baseUrl))
var body: some Scene {
WindowGroup {
ContentView().environmentObject(model)
}
}
}
It giving error ..Cannot convert value of type 'URLSession.Type' to expected argument type 'URLSession'
Here is attempt for URLSession instance.
#main
struct HomwWorkWithSwiftUIApp: App {
init() {
}
var url : URLSession
init(url: URLSession) {
self.url = url
}
#StateObject var model = FruitsModel(fruitRepository: RealFruitsRepository(session: url, baseURL: EndPoint.baseUrl))
var body: some Scene {
WindowGroup {
ContentView().environmentObject(model)
}
}
}
Here is the screenshot ..
Here is the repository code ..
import Foundation
protocol FruitsRepository: WebRepository {
func loadFruits() async throws -> [Fruits]
}
struct RealFruitsRepository: FruitsRepository {
let session: URLSession
let baseURL: String
init(session: URLSession, baseURL: String) {
self.session = session
self.baseURL = baseURL
}
func loadFruits() async throws -> [Fruits] {
guard let request = try? API.allFruits.urlRequest(baseURL: baseURL) else {
throw APIError.invalidURL
}
guard let data = try? await call(request: request) else {
throw APIError.unexpectedResponse
}
guard let fruits = getDecodedFruitesResopnse(from: data) else {
throw APIError.unexpectedResponse
}
return fruits
}
private func getDecodedFruitesResopnse(from data: Data)-> [Fruits]? {
guard let fruites = try? JSONDecoder().decode([Fruits].self, from: data) else {
return nil
}
return fruites
}
}
extension RealFruitsRepository {
enum API {
case allFruits
case fruitDetails(Fruits)
}
}
extension RealFruitsRepository.API: APICall {
var path: String {
switch self {
case .allFruits:
return "/all"
case let .fruitDetails(fruit):
let encodedName = fruit.name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
return "/name/\(encodedName ?? fruit.name)"
}
}
var method: String {
switch self {
case .allFruits, .fruitDetails:
return "GET"
}
}
var headers: [String: String]? {
return ["Accept": "application/json"]
}
func body() throws -> Data? {
return nil
}
}
Here is the model class ..
import Foundation
import Combine
protocol FruitsModelInput {
func getFruits() async
}
protocol FruitsModelOutput {
var state: FruitViewStates { get }
var fruitRecordsCount: Int { get }
func getFruit(index: Int)-> Fruits
func getFruitsDetails(for row:Int)-> FruitsDetails
}
struct FruitsDetails {
let genus, name: String
}
final class FruitsModel: ObservableObject {
private var fruitsRepository: FruitsRepository
var fruits: [Fruits] = []
#Published var state: FruitViewStates = .none
private var cancellables:Set<AnyCancellable> = Set()
init(fruitRepository: FruitsRepository) {
self.fruitsRepository = fruitRepository
}
}
extension FruitsModel: FruitsModelOutput {
func getFruitsDetails(for row: Int) -> FruitsDetails {
if row >= 0 {
let fruit = fruits[row]
return FruitsDetails(genus: fruit.genus, name: fruit.name)
}
return FruitsDetails(genus: "", name: "")
}
var fruitRecordsCount: Int {
return fruits.count
}
func getFruit(index: Int) -> Fruits {
if fruits.count > 0 {
return (fruits[index])
} else {
return Fruits(genus: "", name: "", id: 0, family: "", order: "", nutritions: Nutritions(carbohydrates: 0.0, protein: 0.0, fat: 0.0, calories: 0, sugar: 0.0))
}
}
}
extension FruitsModel: FruitsModelInput {
func getFruits() async {
state = .showActivityIndicator
do {
fruits = try await fruitsRepository.loadFruits()
self.state = .showFruitList
} catch let error {
fruits = []
print(error)
state = .showError((error as! APIError).localizedDescription)
}
}
}

Updated Reverse Geolocation

There are a number of examples showing how to do reverse geolocation, but nothing recent on implementation in SwiftUI. My current code uses the iPhone GPS to generate coordinates that are used with maps to show the location. I would also like to display the street address since a map without text indicating the location isn't very helpful.
My Questions:
Do I have all the relevant code to implement reverse geolocation?
I have seen examples using storyboards and print statements to display the location, but how do I return the location to a Swiftui view with an #escaping closure?
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
#Published var currentAddress: String = ""
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.distanceFilter = 10 // distance before update (meters)
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.startUpdatingLocation()
}
func startLocationServices() {
if locationManager.authorizationStatus == .authorizedAlways || locationManager.authorizationStatus == .authorizedWhenInUse {
locationManager.startUpdatingLocation()
} else {
locationManager.requestWhenInUseAuthorization()
}
}
func getLocationCoordinates() -> (Double, Double) {
let coordinate = self.locationManager.location != nil ? self.locationManager.location!.coordinate : CLLocationCoordinate2D()
print("location = \(coordinate.latitude), \(coordinate.longitude)")
return (Double(coordinate.latitude), Double(coordinate.longitude))
}
// Using closure
func getAddress(handler: #escaping (String) -> Void)
{
self.currentAddress = ""
let coordinate = self.locationManager.location != nil ? self.locationManager.location!.coordinate : CLLocationCoordinate2D()
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark?
placeMark = placemarks?[0]
guard let placemark = placemarks?.first else { return }
if let streetNumber = placemark.subThoroughfare,
let street = placemark.subThoroughfare,
let city = placemark.locality,
let state = placemark.administrativeArea {
DispatchQueue.main.async {
self.currentAddress = "\(streetNumber) \(street) \(city) \(state)"
}
} else if let city = placemark.locality, let state = placemark.administrativeArea {
DispatchQueue.main.async {
self.currentAddress = "\(city) \(state)"
}
} else {
DispatchQueue.main.async {
self.currentAddress = "Address Unknown"
}
}
}
)
print( self.currentAddress)
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if locationManager.authorizationStatus == .authorizedAlways || locationManager.authorizationStatus == .authorizedWhenInUse {
locationManager.startUpdatingLocation()
}
}
// Get Placemark
func getPlace(for location: CLLocation,
completion: #escaping (CLPlacemark?) -> Void) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
guard error == nil else {
print("*** Error in \(#function): \(error!.localizedDescription)")
completion(nil)
return
}
guard let placemark = placemarks?[0] else {
print("*** Error in \(#function): placemark is nil")
completion(nil)
return
}
completion(placemark)
}
}
}
If I add the follow code say in ContentView:
#State private var entryLat: Double = 0.0
#State private var entryLong: Double = 0.0
let result = lm.getLocationCoordinates()
entryLat = result.0
entryLong = result.1
How would I call getPlace?
To use the following code you need to setup the appropriate entitlements and authorizations.
Here is a working example of using geolocation in swiftui, from code I got from
a number of sources on the net years ago.
This should give you a base to do reverse geolocation in swiftui:
import Foundation
import CoreLocation
import SwiftUI
import Combine
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
let locationProvider = LocationProvider()
#State var currentAddress = ""
var body: some View {
Text(currentAddress)
.onAppear {
getAddress()
}
}
func getAddress() {
// for testing Tokyo
let location = CLLocation(latitude: 35.684602, longitude: 139.751992)
locationProvider.getPlace(for: location) { plsmark in
guard let placemark = plsmark else { return }
if let streetNumber = placemark.subThoroughfare,
let street = placemark.subThoroughfare,
let city = placemark.locality,
let state = placemark.administrativeArea {
self.currentAddress = "\(streetNumber) \(street) \(city) \(state)"
} else if let city = placemark.locality, let state = placemark.administrativeArea {
self.currentAddress = "\(city) \(state)"
} else {
self.currentAddress = "Address Unknown"
}
}
}
}
/**
A Combine-based CoreLocation provider.
On every update of the device location from a wrapped `CLLocationManager`,
it provides the latest location as a published `CLLocation` object and
via a `PassthroughSubject<CLLocation, Never>` called `locationWillChange`.
*/
public class LocationProvider: NSObject, ObservableObject {
private let lm = CLLocationManager()
/// Is emitted when the `location` property changes.
public let locationWillChange = PassthroughSubject<CLLocation, Never>()
/**
The latest location provided by the `CLLocationManager`.
Updates of its value trigger both the `objectWillChange` and the `locationWillChange` PassthroughSubjects.
*/
#Published public private(set) var location: CLLocation? {
willSet {
locationWillChange.send(newValue ?? CLLocation())
}
}
/// The authorization status for CoreLocation.
#Published public var authorizationStatus: CLAuthorizationStatus?
/// A function that is executed when the `CLAuthorizationStatus` changes to `Denied`.
public var onAuthorizationStatusDenied : ()->Void = {presentLocationSettingsAlert()}
/// The LocationProvider intializer.
///
/// Creates a CLLocationManager delegate and sets the CLLocationManager properties.
public override init() {
super.init()
self.lm.delegate = self
self.lm.desiredAccuracy = kCLLocationAccuracyBest
self.lm.activityType = .fitness
self.lm.distanceFilter = 10
self.lm.allowsBackgroundLocationUpdates = true
self.lm.pausesLocationUpdatesAutomatically = false
self.lm.showsBackgroundLocationIndicator = true
}
/**
Request location access from user.
In case, the access has already been denied, execute the `onAuthorizationDenied` closure.
The default behavior is to present an alert that suggests going to the settings page.
*/
public func requestAuthorization() -> Void {
if self.authorizationStatus == CLAuthorizationStatus.denied {
onAuthorizationStatusDenied()
}
else {
self.lm.requestWhenInUseAuthorization()
}
}
/// Start the Location Provider.
public func start() throws -> Void {
self.requestAuthorization()
if let status = self.authorizationStatus {
guard status == .authorizedWhenInUse || status == .authorizedAlways else {
throw LocationProviderError.noAuthorization
}
}
else {
/// no authorization set by delegate yet
#if DEBUG
print(#function, "No location authorization status set by delegate yet. Try to start updates anyhow.")
#endif
/// In principle, this should throw an error.
/// However, this would prevent start() from running directly after the LocationProvider is initialized.
/// This is because the delegate method `didChangeAuthorization`,
/// setting `authorizationStatus` runs only after a brief delay after initialization.
//throw LocationProviderError.noAuthorization
}
self.lm.startUpdatingLocation()
}
/// Stop the Location Provider.
public func stop() -> Void {
self.lm.stopUpdatingLocation()
}
// todo deal with errors
public func getPlace(for location: CLLocation, completion: #escaping (CLPlacemark?) -> Void) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
guard error == nil else {
print("=====> Error \(error!.localizedDescription)")
completion(nil)
return
}
guard let placemark = placemarks?.first else {
print("=====> Error placemark is nil")
completion(nil)
return
}
completion(placemark)
}
}
}
/// Present an alert that suggests to go to the app settings screen.
public func presentLocationSettingsAlert(alertText : String? = nil) -> Void {
let alertController = UIAlertController (title: "Enable Location Access", message: alertText ?? "The location access for this app is set to 'never'. Enable location access in the application settings. Go to Settings now?", preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsUrl = URL(string:UIApplication.openSettingsURLString) else {
return
}
UIApplication.shared.open(settingsUrl)
}
alertController.addAction(settingsAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
UIApplication.shared.windows[0].rootViewController?.present(alertController, animated: true, completion: nil)
}
/// Error which is thrown for lacking localization authorization.
public enum LocationProviderError: Error {
case noAuthorization
}
extension LocationProvider: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.authorizationStatus = status
#if DEBUG
print(#function, status.name)
#endif
//print()
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
self.location = location
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
if let clErr = error as? CLError {
switch clErr {
case CLError.denied : do {
print(#function, "Location access denied by user.")
self.stop()
self.requestAuthorization()
}
case CLError.locationUnknown : print(#function, "Location manager is unable to retrieve a location.")
default: print(#function, "Location manager failed with unknown CoreLocation error.")
}
}
else {
print(#function, "Location manager failed with unknown error", error.localizedDescription)
}
}
}
extension CLAuthorizationStatus {
/// String representation of the CLAuthorizationStatus
var name: String {
switch self {
case .notDetermined: return "notDetermined"
case .authorizedWhenInUse: return "authorizedWhenInUse"
case .authorizedAlways: return "authorizedAlways"
case .restricted: return "restricted"
case .denied: return "denied"
default: return "unknown"
}
}
}

Swift | Set with NSObject

I'm trying to create a Set with custom objects.
This is working, If I use a Set of my custom objects there is no duplicates :
public class AttributesGroup: Hashable, Equatable, Comparable {
open var id: Int!
open var name: String!
open var position: Int!
public init (id: Int = 0, name: String = "", position: Int = 0) {
self.id = id
self.name = name
self.position = position
}
open var hashValue: Int {
get {
return id.hashValue
}
}
public static func ==(lhs: AttributesGroup, rhs: AttributesGroup) -> Bool {
return lhs.id == rhs.id
}
public static func < (lhs: AttributesGroup, rhs:AttributesGroup) -> Bool {
return lhs.position < rhs.position
}
}
I extend my class with NSObject, since NSObject already implements Hashable protocol (and also Equatable) I have to override hashValue, and this is not working anymore, If I use a Set of my custom objects there is duplicates, what do I do wrong here ? :
public class AttributesGroup: NSObject, Comparable {
open var id: Int!
open var name: String!
open var position: Int!
public init (id: Int = 0, name: String = "", position: Int = 0) {
self.id = id
self.name = name
self.position = position
}
open override var hashValue: Int {
get {
return id.hashValue
}
}
public static func ==(lhs: AttributesGroup, rhs: AttributesGroup) -> Bool {
return lhs.id == rhs.id
}
public static func < (lhs: AttributesGroup, rhs:AttributesGroup) -> Bool {
return lhs.position < rhs.position
}
}
Thanks for your help !
NSObject is a Cocoa type. The rules for NSObject are different from the rules for Swift. To make an NSObject work in a set, it must have an implementation of isEqual consonant with its implementation of hash.

Deletebackward() Swift 3

DeleteBackward() deletes only one character, is there any way to keep on deleting backwards ?
I am using emojiKeyboard and I have a delete emoticon. I detect the emoji being the delete emoticon and I call
if emoticon.isDelete{
deleteBackward()
return
}
Update:
Steven's solution works on buttons but not on my UITextView. Will try and find out why. I have tried having the addGestureRecognizer in ViewWillAppear as well as ViewDidLoad.
This should get you started, didn't test but should do the trick.
fileprivate var timer = Timer()
fileprivate var textField = UITextField() //change to your field
override func viewDidLoad() {
super.viewDidLoad()
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:)))
textField.addGestureRecognizer(longPress)
}
func longPress(_ guesture: UILongPressGestureRecognizer) {
if guesture.state == UIGestureRecognizerState.began {
longPressBegun(guesture)
} else if guesture.state == UIGestureRecognizerState.changed {
//longPressStateChanged(guesture)
} else if guesture.state == UIGestureRecognizerState.ended {
longPressEnded()
} else if guesture.state == UIGestureRecognizerState.cancelled {
longPressCancelled()
}
}
func longPressBegun(_ guesture: UILongPressGestureRecognizer) {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(repeatAction), userInfo: nil, repeats: true)
}
func longPressEnded() {
timer.invalidate()
}
func longPressCancelled() {
timer.invalidate()
}
func repeatAction() {
deleteBackward()
}

Update NSArrayController correctly

I'm trying to populate NSTableView using NSArrayController, however can't get it to work. Here is my code:
class AppDelegate: NSObject, NSApplicationDelegate {
private let _spadList: SpadList
var spadList : SpadList {
get { return _spadList }
}
override init() {
_spadList = SpadList()
super.init()
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
createInitialData()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func createInitialData() {
_spadList.chain = "CHAIN"
_spadList.service = "Service"
_spadList.dateString = "2016-12-12"
let firstEquity = Equity()
let anotherEquity = Equity()
firstEquity.name = "Apple"
firstEquity.tradePrice = 12.3
anotherEquity.name = "ORACLE"
anotherEquity.tradePrice = 45.7
_spadList.addEquity(equity: firstEquity)
_spadList.addEquity(equity: anotherEquity)
}
}
And this is ViewController:
class ViewController: NSViewController {
let appDelegate = NSApplication.shared().delegate as! AppDelegate
#IBOutlet weak var tableView: NSTableView!
func equities() -> [Equity]{
return appDelegate.spadList.equities
}
}
Content Array of the NSArrayController is bound to: ViewController.equities
The issue is that my manually created data are not populating itself into my tableView. If I move createInitialData() to ViewController class, they are correctly displayed.
What am I doing wrong?