macOS menu bar text with icon - swift

As you can see in the image I would like to be able to do a similar one, to make a way that instead of showing only the icon of the sun, also showing a text.
As seen in the image below, an icon followed by a text.
But I only managed to do this:
The problem I would like to put the icon on the left or right of the text, not above it, can you give me a hand?
P.s.
The text must change accordingly, how can I make the StatusBarController receive the text changes.
import AppKit
import SwiftUI
class StatusBarController {
#ObservedObject var userPreferences = UserPreferences.instance
private var statusBar: NSStatusBar
private var statusItem: NSStatusItem
private var popover: NSPopover
init(_ popover: NSPopover) {
self.popover = popover
statusBar = NSStatusBar.init()
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let statusBarButton = statusItem.button {
if let _ = userPreferences.$inDownload {
statusItem.button?.title = userPreferences.$percentualDownload
}
statusBarButton.image = #imageLiteral(resourceName: "Weather")
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
}
}
#objc func togglePopover(sender: AnyObject) {
if(popover.isShown) {
hidePopover(sender)
}
else {
showPopover(sender)
}
}
func showPopover(_ sender: AnyObject) {
if let statusBarButton = statusItem.button {
popover.show(relativeTo: statusBarButton.bounds, of: statusBarButton, preferredEdge: NSRectEdge.maxY)
}
}
func hidePopover(_ sender: AnyObject) {
popover.performClose(sender)
}
}
I'm thinking of using something like that:
import EventKit
import ServiceManagement
private struct PreferencesKeys {
static let backgroundIsTransparent = "backgroundIsTransparent"
static let inDownload = "inDownload"
static let percentualDownload = "percentualDownload"
}
class UserPreferences: ObservableObject {
static let instance = UserPreferences()
private init() {
// This prevents others from using the default '()' initializer for this class.
}
private static let defaults = UserDefaults.standard
#Published var backgroundIsTransparent: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.backgroundIsTransparent) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.backgroundIsTransparent)
}() {
didSet {
UserPreferences.defaults.set(backgroundIsTransparent, forKey: PreferencesKeys.backgroundIsTransparent)
}
}
#Published var inDownload: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.inDownload) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.inDownload)
}() {
didSet {
UserPreferences.defaults.set(inDownload, forKey: PreferencesKeys.inDownload)
}
}
#Published var percentualDownload: String = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.percentualDownload) != nil else {
return "0%"
}
return UserDefaults.standard.string(forKey: PreferencesKeys.percentualDownload)!
}() {
didSet {
UserPreferences.defaults.set(percentualDownload, forKey: PreferencesKeys.percentualDownload)
}
}
}
but I get the following error:
Edit:
First problem solved I used:
statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
statusBarButton.imagePosition = NSControl.ImagePosition.imageRight
For the update text problem, what can I do?

Related

SwiftUI: How to Call a Function when Photo Picker Closes

I'm using a photo picker in my SwiftUI class to load photos and videos into an array. Right now I'm displaying those images after they've been selected in the picker. Works fine.
Instead, I'd like to run a function when I click the "Add" button and upload the objects in that array to Cloudinary for processing and storage. I can manually make this happen with a separate button outside the picker, but for the best UX, I think this function should run automatically when that "Add" button is selected.
How do I run a function when that "Add" button is clicked? Do I need to check if the array is not empty and some other condition exists instead?
Here's an image of the picker:
Here's the picker code:
import SwiftUI
import PhotosUI
struct PhotoPicker: UIViewControllerRepresentable {
typealias UIViewControllerType = PHPickerViewController
#ObservedObject var mediaItems: PickedMediaItems
var didFinishPicking: (_ didSelectItems: Bool) -> Void
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration()
config.filter = .any(of: [.images, .videos, .livePhotos])
config.selectionLimit = 0
config.preferredAssetRepresentationMode = .current
let controller = PHPickerViewController(configuration: config)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(with: self)
}
class Coordinator: PHPickerViewControllerDelegate {
var photoPicker: PhotoPicker
init(with photoPicker: PhotoPicker) {
self.photoPicker = photoPicker
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
photoPicker.didFinishPicking(!results.isEmpty)
guard !results.isEmpty else {
return
}
for result in results {
let itemProvider = result.itemProvider
guard let typeIdentifier = itemProvider.registeredTypeIdentifiers.first,
let utType = UTType(typeIdentifier)
else { continue }
if utType.conforms(to: .image) {
self.getPhoto(from: itemProvider, isLivePhoto: false)
} else if utType.conforms(to: .movie) {
self.getVideo(from: itemProvider, typeIdentifier: typeIdentifier)
} else {
self.getPhoto(from: itemProvider, isLivePhoto: true)
}
}
}
private func getPhoto(from itemProvider: NSItemProvider, isLivePhoto: Bool) {
let objectType: NSItemProviderReading.Type = !isLivePhoto ? UIImage.self : PHLivePhoto.self
if itemProvider.canLoadObject(ofClass: objectType) {
itemProvider.loadObject(ofClass: objectType) { object, error in
if let error = error {
print(error.localizedDescription)
}
if !isLivePhoto {
if let image = object as? UIImage {
DispatchQueue.main.async {
self.photoPicker.mediaItems.append(item: PhotoPickerModel(with: image))
}
}
} else {
if let livePhoto = object as? PHLivePhoto {
DispatchQueue.main.async {
self.photoPicker.mediaItems.append(item: PhotoPickerModel(with: livePhoto))
}
}
}
}
}
}
private func getVideo(from itemProvider: NSItemProvider, typeIdentifier: String) {
itemProvider.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { url, error in
if let error = error {
print(error.localizedDescription)
}
guard let url = url else { return }
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
guard let targetURL = documentsDirectory?.appendingPathComponent(url.lastPathComponent) else { return }
do {
if FileManager.default.fileExists(atPath: targetURL.path) {
try FileManager.default.removeItem(at: targetURL)
}
try FileManager.default.copyItem(at: url, to: targetURL)
DispatchQueue.main.async {
self.photoPicker.mediaItems.append(item: PhotoPickerModel(with: targetURL))
}
} catch {
print(error.localizedDescription)
}
}
}
}
}
I added this class to the View:
class PickerStatus: ObservableObject {
var status: Bool = false
}
Then added this line to the PhotoPicker:
#ObservedObject var finishedSelection: PickerStatus
Then in the Coordinator, I added this:
for result in results {
self.photoPicker.finishedSelection.status = true
...
}
Now in my View I can set the instance of the ObservedObject, pass it into my child views including the PhotoPicker and check the value of that same Observed Object:
#ObservedObject var finishedSelection = PickerStatus()

Swift ObservedObject 'self' used in property access 'userPreferences' before all stored properties are initialized

I'm having the following problem can you tell me where I'm wrong?
import EventKit
import ServiceManagement
private struct PreferencesKeys {
static let backgroundIsTransparent = "backgroundIsTransparent"
static let isDarkMode = "isDarkMode"
}
class UserPreferences: ObservableObject {
static let instance = UserPreferences()
private init() {
// This prevents others from using the default '()' initializer for this class.
}
private static let defaults = UserDefaults.standard
#Published var backgroundIsTransparent: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.backgroundIsTransparent) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.backgroundIsTransparent)
}() {
didSet {
UserPreferences.defaults.set(backgroundIsTransparent, forKey: PreferencesKeys.backgroundIsTransparent)
}
}
#Published var isDarkMode: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.isDarkMode) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.isDarkMode)
}() {
didSet {
UserPreferences.defaults.set(isDarkMode, forKey: PreferencesKeys.isDarkMode)
}
}
}
This is a common mistake.
You are going to modify popover before having initialized statusItem. This breaks the rules and this is what the error says.
First initialize statusItem then set the background color of popover.
I would initialize statusItem even
let statusItem = NSStatusbar.system.status...
The code (text) in the question is irrelevant.

Remove annotation when slider moves - swift

i have a problem with annotations that i can't resolve. When you click on a UIButton, the #IBAction pressPlay function starts, which causes the slider on my map to start moving. The slider has the max value 0 and min -31, and the initial value is 0 and it starts to move only if the thumb is in position! = From 0 and moves every 1 second. This works correctly moves the slider.
#IBAction func pressPlay(_ sender: Any)
{
let calendar2 = Calendar.current
let today = Date()
var cnt = Int(sliderTime.value)
let play = UIImage(named: "play")
let pause = UIImage(named: "pause")
let format = DateFormatter()
playButton.setImage(play, for: .normal)
if control == true && Int(sliderTime.value) < 0
{ //mette in play
control = false
playButton.setImage(pause, for: .normal)
//removeSeismometers = true
if Int(sliderTime.value) < 0
{
timer = Timer.scheduledTimer(withTimeInterval: 1,repeats: true)
{ [self]t in //ogni secondo questo timer cambia il valore dell'alpha del pin che sta vibrando
if cnt < 0
{
cnt = Int(self.sliderTime.value)
self.sliderTime.value += 1
let newDate2 = calendar2.date(byAdding: .day, value: Int(self.sliderTime.value), to:today)! //sottraggo alla data attuale il vlaore dello slider per tornare indietro nel tempo
format.dateStyle = .medium // "MM/GG/AAAA"
self.labelTime.text = "\(format.string(from: newDate2))"
appo += 1
for i in mapEqAnnotation{
let str: String = i.eq.eventTime
let index = str.index(str.endIndex, offsetBy: -9)
let mySubstring = str[..<index]
nuovaData = calendario.date(byAdding: .day, value: Int(sliderTime.value), to:dataCorrente)!
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
let dataCntr = "\(format.string(from: nuovaData))"
if mySubstring == dataCntr{
printQuake(quake: i)
}else{
removeQuake(quake:i)
}
}
//printQuake(sliderValue: appo)
}else if cnt == 0{
//removeSeismometers = false
playButton.setImage(play, for: .normal)
timer!.invalidate()
}
}
}
}else if control == false && Int(sliderTime.value) < 0 {
playButton.setImage(play, for: .normal)
control = true
timer!.invalidate()
}
}
My problem is that every second the slider has to move when you click on the UIButton, and every second has to add an annotation to the map and remove it as soon as you move the slider again.
Everything works, except that when the slider scrolls, the annotations of the previous move do not disappear, but remain on the map
func printQuake(quake: MapEarthquakeAnnotation){
let q = MapEarthquakeAnnotation(eq:quake.eq)
mapView.addAnnotation(q)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation is MapEarthquakeAnnotation{
annotazioni.append(annotation)
let EQAnnotation = annotation as! MapEarthquakeAnnotation
var view: MKPinAnnotationView
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: EQAnnotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.brown
return view
}else if (annotation is MapSeismometerAnnotation) {
if let annotation = annotation as? MapSeismometerAnnotation
{
var view: MKPinAnnotationView
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.green
view.image = UIImage(named: "pin-verde")
return view
}
return nil
}
return nil
}
Can you give me some advice?
It would be helpful to see the code you use in removeQuake but after a quick view of your code a very likely candidate is the code
func printQuake(quake: MapEarthquakeAnnotation){
let q = MapEarthquakeAnnotation(eq:quake.eq)
mapView.addAnnotation(q)
}
Here you create a new annotation each time you call this method. And this annotation is not preserved anywhere. So when you call removeQuake(quake:i) you can not expect that i is any of the annotations you have added using printQuake.
I am not sure why this code was build the it was but it is possible all you need to do is change this method to
func printQuake(quake: MapEarthquakeAnnotation){
mapView.addAnnotation(quake)
}
In general your code is a bit hard to read. You should look into more modern approaches using Swift and you should try to split parts of code so that they are easier to read and maintain. Non-English languages are not very helpful either.
I tried to tear down and reconstruct your code. Not everything is there and I am not sure it does what you want it to do. But still please inspect it. Maybe it will help you fix the issue you have.
import UIKit
import MapKit
class ViewController: UIViewController {
#IBOutlet private var mapView: MKMapView!
#IBOutlet private var sliderTime: UISlider!
#IBOutlet private var playButton: UIButton!
#IBOutlet private var labelTime: UILabel!
private var timer: Timer?
private var earthquakeAnnotations: [MapEarthquakeAnnotation] = []
private var dayOffset: Int = -30 {
didSet {
refresh()
}
}
private var isPlaying: Bool = false {
didSet {
playButton.setImage(isPlaying ? UIImage(named: "play") : UIImage(named: "pause"), for: .normal)
}
}
private func refresh() {
let beginningOfToday: Date = Calendar.autoupdatingCurrent.date(from: Calendar.autoupdatingCurrent.dateComponents([.year, .month, .day], from: Date()))!
let presentedDay = Calendar.autoupdatingCurrent.date(byAdding: .day, value: dayOffset, to: beginningOfToday)!
refreshSlider()
refreshTimeLabel(date: presentedDay)
refreshAnnotations(date: presentedDay)
}
private func refreshSlider() {
sliderTime.value = Float(dayOffset)
}
private func refreshTimeLabel(date: Date) {
labelTime.text = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: date)
}()
}
private func refreshAnnotations(date: Date) {
earthquakeAnnotations.forEach { annotation in
if annotation.eq.date == date {
if !mapView.annotations.contains(where: { $0 === annotation }) {
mapView.addAnnotation(annotation)
}
} else {
if mapView.annotations.contains(where: { $0 === annotation }) {
mapView.removeAnnotation(annotation)
}
}
}
}
private func stopPlaying() {
timer?.invalidate()
timer = nil
isPlaying = false
}
private func startPlaying() {
if dayOffset < 0 {
isPlaying = true
timer?.invalidate() // Just in case
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
if self.dayOffset < 0 {
self.dayOffset += 1
} else {
self.stopPlaying()
}
})
} else {
// Already at the end. Should restart?
}
}
#IBAction func pressPausePlay(_ sender: Any) {
if isPlaying {
stopPlaying()
} else {
startPlaying()
}
}
}
// MARK: - MKMapViewDelegate
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? MapEarthquakeAnnotation {
let view: MKPinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.brown
return view
} else if let annotation = annotation as? MapSeismometerAnnotation {
let view: MKPinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.green
view.image = UIImage(named: "pin-verde")
return view
} else {
return nil
}
}
}
// MARK: - MapEarthquakeAnnotation
private extension ViewController {
class MapEarthquakeAnnotation: NSObject, MKAnnotation {
var identifier: String { "MapEarthquakeAnnotationID" }
let coordinate: CLLocationCoordinate2D
let eq: Earthquake
init(earthquake: Earthquake, coordinate: CLLocationCoordinate2D) { self.eq = earthquake; self.coordinate = coordinate }
}
}
// MARK: - MapSeismometerAnnotation
private extension ViewController {
class MapSeismometerAnnotation: NSObject, MKAnnotation {
var identifier: String { "MapSeismometerAnnotationID" }
let coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D) { self.coordinate = coordinate }
}
}
// MARK: - Earthquake
private extension ViewController {
struct Earthquake {
let eventTime: String
var date: Date {
let index = eventTime.index(eventTime.endIndex, offsetBy: -9)
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
return format.date(from: String(eventTime[..<index])) ?? Date()
}
}
}

Swift: Notification permissions pop up affecting class functionality

This is a hard one to explain as I'm not entirely sure what's going wrong here. In my AppDelegate I add an array of services to the app:
override var services: [ApplicationService] {
if UIApplication.isRunningUnitTests() {
return []
}
return [
SettingsService(),
TrackingApplicationService(),
NotificationApplicationService(),
AudioApplicationService(),
ConnectionService()
]
}
The connection service is the one I am interested in here. It looks like this:
import PluggableAppDelegate
import Connectivity
final class ConnectionService: NSObject, ApplicationService {
// MARK: - Helpers
private struct Constants {
static let containerHeight: CGFloat = 20
static let containerPadding: CGFloat = 20
static let statusCenterAdjustment: CGFloat = -1
static let statusFont: UIFont = .systemFont(ofSize: 15, weight: .semibold)
static let labelLines = 1
static let radiusDenominator: CGFloat = 2.0
static let animationDuration = 0.2
static let defaultAnchorConstant: CGFloat = 0
}
internal enum ConnectionStatus {
case connected
case disconnected
}
// MARK: - Properties
private let connectivity = Connectivity()
private let queue = DispatchQueue.global(qos: .utility)
internal var currentStatus = ConnectionStatus.connected
private var statusTopAnchor: NSLayoutConstraint?
private lazy var statusContainer: UIView = {
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
container.backgroundColor = UIColor.red
container.layer.cornerRadius = Constants.containerHeight / Constants.radiusDenominator
container.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
container.isHidden = true
container.addSubview(statusLabel)
NSLayoutConstraint.activate([
statusLabel.centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: Constants.statusCenterAdjustment),
statusLabel.leftAnchor.constraint(equalTo: container.leftAnchor, constant: Constants.containerPadding),
statusLabel.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -Constants.containerPadding)
])
return container
}()
private lazy var statusLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = Constants.labelLines
label.font = Constants.statusFont
label.textAlignment = .center
label.textColor = .white
label.text = Strings.common.user.noInternetConnection.localized
return label
}()
// MARK: - Lifecycle
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
DispatchQueue.main.async { [weak self] in
self?.prepareUI()
}
prepareConnectivityListener()
updateConnectionStatus(connectivity.status)
return true
}
// MARK: - Connectivity
func updateConnectionStatus(_ status: Connectivity.Status) {
switch status {
case .connected, .connectedViaWiFi, .connectedViaCellular:
updateLabelWith(connectionStatus: .connected)
case .notConnected, .connectedViaWiFiWithoutInternet, .connectedViaCellularWithoutInternet:
updateLabelWith(connectionStatus: .disconnected)
}
NotificationCenter.default.post(name: .connectivityStatusChanged, object: self.currentStatus)
}
// MARK: - Configuration
private func prepareUI() {
guard let window = UIApplication.shared.keyWindow else {
assertionFailure("We require a Window")
return // Don't setup in release if we hit this, no point
}
window.addSubview(statusContainer)
statusTopAnchor = statusContainer.topAnchor.constraint(equalTo: window.topAnchor, constant: -Constants.containerHeight)
guard let statusTopAnchor = statusTopAnchor else {
assertionFailure("Top anchor could not be build")
return
}
NSLayoutConstraint.activate([
statusTopAnchor,
statusContainer.centerXAnchor.constraint(equalTo: window.centerXAnchor),
statusContainer.heightAnchor.constraint(equalToConstant: Constants.containerHeight)
])
}
private func prepareConnectivityListener() {
let connectivityChanged: (Connectivity) -> Void = { [weak self] connectivity in
self?.updateConnectionStatus(connectivity.status)
}
connectivity.whenConnected = connectivityChanged
connectivity.whenDisconnected = connectivityChanged
connectivity.startNotifier(queue: queue)
}
private func updateLabelWith(connectionStatus: ConnectionStatus) {
if currentStatus == connectionStatus {
return // No need to update if we are the same state
} else {
currentStatus = connectionStatus
}
let topAnchorConstant: CGFloat = (connectionStatus == .connected) ? -Constants.containerHeight : Constants.defaultAnchorConstant
DispatchQueue.main.async { [weak self] in
DispatchQueue.main.async { [weak self] in
if connectionStatus != .connected {
self?.statusContainer.isHidden = false
}
self?.statusTopAnchor?.constant = topAnchorConstant
UIView.animate(withDuration: Constants.animationDuration,
animations: { [weak self] in
self?.statusContainer.superview?.layoutIfNeeded()
},
completion: { _ in
if connectionStatus == .connected {
self?.statusContainer.isHidden = true
}
})
}
}
}
}
So I'm using this service to check the connection status of the device and on the login view controller I display different warnings depending on the connection status.
However, my problem is is that the connection service is not functioning properly when the permissions pop up appears. As you can see from the ConnectionService class, an alert should be generated when the app is offline. Now because I am adding this connection service before the permissions are requested, the window briefly shows the alert, but as soon as the permissions alert appears, it disappears.
Furthermore, you can see in ConnectionService.swift I have a listener set up:
NotificationCenter.default.post(name: .connectivityStatusChanged, object: self.currentStatus)
I'm listening for this in the subsequent viewController which triggers this method:
#objc private func connectivityChanged(notification: Notification) {
if notification.object as? ConnectionService.ConnectionStatus == .connected {
deviceConnected = true
} else {
deviceConnected = false
}
}
This in turn triggers a UI change:
private var deviceConnected: Bool = true {
willSet {
DispatchQueue.main.async {
if newValue == true {
self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_completed")
self.errorLabel?.isHidden = true
} else {
self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_offline")
self.errorLabel?.isHidden = false
self.errorLabel?.text = Strings.eHandshake.warning.deviceOffline.title.localized
}
}
}
}
When permissions have already been granted and therefore the app launches without requesting permission, this works fine, but when the permissions alert appears, this seems to break this part of the logic.
To be clear the services var is initially declared here:
public protocol ApplicationService: UIApplicationDelegate {}
extension ApplicationService {
public var window: UIWindow? {
return UIApplication.shared.delegate?.window ?? nil
}
}
open class PluggableApplicationDelegate: UIResponder,
UIApplicationDelegate {
public var window: UIWindow?
open var services: [ApplicationService] { return [] }
internal lazy var _services: [ApplicationService] = {
return self.services
}()
#discardableResult
internal func apply<T, S>(_ work: (ApplicationService, #escaping (T) -> Void) -> S?, completionHandler: #escaping ([T]) -> Swift.Void) -> [S] {
let dispatchGroup = DispatchGroup()
var results: [T] = []
var returns: [S] = []
for service in _services {
dispatchGroup.enter()
let returned = work(service, { result in
results.append(result)
dispatchGroup.leave()
})
if let returned = returned {
returns.append(returned)
} else { // delegate doesn't implement method
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completionHandler(results)
}
return returns
}
}
Any help with this would be much appreciated.

How to use presentViewController in a custom class?

I just update my code, but the alertWindow won't show up.
I add break point in Xcode, it shows that the viewDelegate is nil.
All delegates are nil.
This is my code:
class LoginViewController:UIViewController,AlertDelegate
{
override func viewDidLoad() {
super.viewDidLoad()
let BackGroundImage:UIImageView = UIImageView(frame: CGRectMake(0, 0, self.view.frame.width , self.view.frame.height))
let image: UIImage = UIImage(named: "backgroundLogin.jpg")!
BackGroundImage.image = image
self.view.addSubview(BackGroundImage)
//self.view.backgroundColor = UIColor(patternImage: UIImage(named: "backgroundLogin.jpg")!)
username.text = "User name"
password.text = "Password"
usernameWarning.text = ""
passwordWarning.text = ""
self.view.bringSubviewToFront(username)
self.view.bringSubviewToFront(password)
self.view.bringSubviewToFront(usernameText)
self.view.bringSubviewToFront(passwordText)
self.view.bringSubviewToFront(LoginButton)
self.view.bringSubviewToFront(RegisterButton)
self.view.bringSubviewToFront(usernameWarning)
self.view.bringSubviewToFront(passwordWarning)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier
{
case .Some("RegisterSegue"):
let registerViewController = segue.destinationViewController as! RegisterViewController
if let temp_register_username = usernameText!.text
{
registerViewController.register_name = temp_register_username
if let temp_register_pass = passwordText!.text
{
registerViewController.register_password = temp_register_pass
}
}
case .Some("loginSegue"):
let loginViewController = segue.destinationViewController as! FunctionsViewController
loginViewController.client = client
default:
super.prepareForSegue(segue, sender: sender)
}
}
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if identifier == "loginSegue"
{
if usernameText.text != ""
{
if passwordText.text != ""
{
let encrypt = encryptMsg(send: passwordText.text!)
let encrypt_pass = encrypt.encryptSendMsg()
let sendMsg = AppConfig.AddHead("login", body: usernameText.text!+"|"+encrypt_pass)
let (_,_) = client.connect(timeout: 1)
let (success,_) = client.send(str: sendMsg)
if success
{
let data = client.read(1024*10)
if let d = data
{
if let str = String(bytes: d, encoding: NSUTF8StringEncoding)
{
if str[0...2] == AppConfig.TransmissionAgreementConfiguration["successLogin"]
{
AppConfig.StoreUserID(str)
let defaults = NSUserDefaults()
defaults.setObject(usernameText.text!, forKey: "userName")
let acceptThread = AcceptThread()
acceptThread.start()
return true
}
else if str[0...2] == AppConfig.TransmissionAgreementConfiguration["errLoginUsernameNotExist"]
{
usernameWarning.text = "User name doesn't exist"
usernameWarning.reloadInputViews()
passwordWarning.text = ""
passwordWarning.reloadInputViews()
return false
}
else if str[0...2] == AppConfig.TransmissionAgreementConfiguration["errLoginUsernameAlreadyOnline"]
{
usernameWarning.text = "User account already be online"
usernameWarning.reloadInputViews()
passwordWarning.text = ""
passwordWarning.reloadInputViews()
return false
}
else if str[0...2] == AppConfig.TransmissionAgreementConfiguration["errLoginPassword"]
{
usernameWarning.text = ""
usernameWarning.reloadInputViews()
passwordWarning.text = "Password Incorrect"
passwordWarning.reloadInputViews()
return false
}
}
}
}
return false
}
else
{
usernameWarning.text = ""
usernameWarning.reloadInputViews()
passwordWarning.text = "password cannot be empty"
passwordWarning.reloadInputViews()
return false
}
}
else
{
usernameWarning.text = "username cannot be empty"
usernameWarning.reloadInputViews()
return false
}
}
else
{
return true
}
}
func presentWindow(title:String,msg:String)
{
let alertController = UIAlertController(title: "\(title):", message: "\(msg)", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler{
(textField: UITextField!) -> Void in
textField.placeholder = "Message"
}
let cancelAction = UIAlertAction(title: "Ignore", style: UIAlertActionStyle.Cancel, handler: nil)
let okAction = UIAlertAction(title: "Reply", style: UIAlertActionStyle.Default,
handler: {
action in
let reply_msg = alertController.textFields!.first! as UITextField
print("I replies \(reply_msg)")
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
#IBAction func logoutUnwindSegue(segue: UIStoryboardSegue) {
// Intentionally left blank
}
var client:TCPClient = TCPClient(addr:"localhost",port:8889)
var AppConfig=TransmissionAgreement(first:"test")
#IBOutlet weak var usernameWarning: UILabel!
#IBOutlet weak var passwordWarning: UILabel!
#IBOutlet weak var username: UILabel!
#IBOutlet weak var password: UILabel!
#IBOutlet weak var usernameText: UITextField!
#IBOutlet weak var passwordText: UITextField!
#IBOutlet weak var LoginButton: UIButton!
#IBOutlet weak var RegisterButton: UIButton!
}
class AcceptThread: NSThread {
override func main() {
let server:TCPServer = TCPServer(addr:"127.0.0.1",port: 7000)
let (success,_) = server.listen()
if success
{
while !cancelled
{
if let my_client = server.accept()
{
let clientReadThread = ClientReadThread(client: my_client)
clientReadThread.start()
clientThreads.append(clientReadThread)
}
}
for clientThread in clientThreads {
clientThread.cancel()
}
}
}
var clientThreads = Array<ClientReadThread>()
var AppConfig=TransmissionAgreement(first:"test")
}
#objc protocol AlertDelegate: class {
func presentWindow(title:String,msg:String)
}
#objc protocol ClientReadThreadDelegate: class {
func clientReadThread(clientReadThread: ClientReadThread, didReceiveMessage message: String)
}
class ClientReadThread:NSThread{
init(client: TCPClient) {
self.client = client
super.init()
}
override func main() {
while !cancelled, let readValue = client.read(1024*10) {
if let message = String(bytes: readValue, encoding: NSUTF8StringEncoding) {
let head = message[0...2]
// print("head is \(head)")
if head! == AppConfig.TransmissionAgreementConfiguration["add"]
{
let defaults = NSUserDefaults()
let myname = defaults.stringForKey("userName")
//store data
let context = CoreDataService.sharedCoreDataService.mainQueueContext
let friend = NSEntityDescription.insertNewObjectForNamedEntity(Friends.self, inManagedObjectContext: context)
friend.name = message.substringFromIndex(3)
try! context.save()
let sendMsg = AppConfig.AddClientHead("agreeAdd", body:myname)
let (_,_) = client.send(str:sendMsg)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate?.clientReadThread(self, didReceiveMessage: message)
})
}
else if head! == AppConfig.TransmissionAgreementConfiguration["chat"]
{
let search = message.substringFromIndex(3)
let resultController = try? catFriend.sharedCatFriendsService.catBlack(search)
try! resultController!.performFetch()
let flag = resultController?.sections?.count
if flag <= 1//friend
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let client_info = message.split("|")
let client_name = client_info[1]
//self.alertDelegate?.presentWindow(client_name, msg: client_info[2])
self.viewDelegate?.presentWindow(client_name, msg: client_info[2])
})
}
else//black, not show the meg
{
//do nothing
}
}
}
}
}
var AppConfig=TransmissionAgreement(first:"test")
let client: TCPClient
var viewDelegate:LoginViewController?
var delegate: ClientReadThreadDelegate?
var alertDelegate: AlertDelegate?
private var resultController: NSFetchedResultsController?
}
presentViewContoller is a method on UIViewController so you'll need an instance of a view controller to be able to present your alert view.
The way this would normally be done is to have the view controller you have start the ClientReadThread implement the AlertDelegate protocol. There's no reason for ClientReadThread to implement AlertDelegate itself. Would it ever be it's own delegate?
Also you delegate's should be marked weak or else you'll end up with a retain cycle.
class MyViewController : UIViewController, AlertDelegate {
private var clientReadThread: ClientReadThread?
override viewDidLoad() {
super.viewDidLoad();
clientReadThread = ClientReadThread(someTcpClient)
clientReadThread.viewDelegate = self
}
func presentWindow(title:String,msg:String) {
// Your code goes here.
}
}
Your other option would be to pass an instance of a UIViewController into your ClientReadThread and use that to present the alert view. Although that's not the way I would go. the ClientReadThread shouldn't need to know how it's alerts are handled, that should be delegated to another class.