Swift3 – lucrul cu taskuri in background

Avem urmatorul scenariu: o aplicatie player audio care “observa” cand schimbam outputul audio: cand trecem de pe casti pe speaker si viceversa.
Nu intram in detalii aici cum e cu schimbarea rutei audio, poate intr-o discutie viitoare.
Ceea ce vreau sa punctez aici este urmatorul aspect: acest player poate functiona din IOS 8 si in background. Pot asculta muzica si atunci cand blochez ecranul sau comut pe alta aplicatie.
Cum pot permite unei aplicatii scrise in Swift3 sa “lucreze” si in background?

1. pot implementa metoda applicationWillResignActive(_ application: UIApplication)
2. putem inregistra notificarea UIApplicationWillResignActive oriunde in cadrul proiectului nostru Swift.

Cum facem lucrul asta:

1. func applicationWillResignActive(_ application: UIApplication) {
......
DispatchQueue.main.async {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
.....
}

2. NotificationCenter.default.addObserver(
self,
selector: #selector(appDidEnterBackground(notification:)),
name: .UIApplicationWillResignActive ,
object: nil)

unde definim selectorul astfel:

func appDidEnterBackground(notification: NSNotification) {
var bti:UIBackgroundTaskIdentifier=0
bti=self.app.beginBackgroundTask{
UIApplication.shared.endBackgroundTask(bti)
}

Le-am folosit pe ambele in aplicatii, insa o prefer pe a doua din motiv de claritate a codului. Nu e chiar ok sa abuzez de AppDelegate.

Share Button

Nil-Coalescing Operator – Swift

Nu stiu cum sa-i zic pe romaneste acestui operator ?? in Swift. De doua ori semnul intrebarii ar suna ridicol.
Documentatia Apple spune ca a ?? b este prescurtarea de la
a != nil ? a! : b
Ce ar insemna asta?
Daca a nu este nil atunci forteaza pe a sa devina a! – valoare unwrapped, in caz contrar, adica daca a este nil, a devenind b.

Ca sa intelegem mai bine, dam un mic exemplu:

let defaultColorName = "red"
var userDefinedColorName: String? // defaults to nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is nil, so colorNameToUse is set to the default of "red"

userDefinedColorName este implicit nil atunci cand se face initializarea.
Var-ul colorNameToUse ia valoarea userDefinedColorName daca nu este nil (nu este cazul) si in acest caz va lua valoarea defaultColorName.

Share Button

Transmiterea unei imagini in format base64 catre server in Swift3

Avem o imagine. O transformam in string pentru a o putea transfera catre un server tert:

let image = UIImage(...)
let imageData = UIImageJPEGRepresentation(image!, 0.7)
let imageString:String = imageData!.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) as String

Ca si observatie, e important sa stabilim optiunea lineLength64Characters pentru codarea imaginii, acesta fiind formatul folosit de un server Apache pentru decodarea base64.

Avem deci in acest moment un string ce contine o imagine pe care dorim sa o trimitem spre un server web.

Folosim un request de tip POST in care specificam metoda de codare x-www-form-urlencoded:

request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

In momentul in care facem request-ul totul este frumos in aplicatia IOS insa pe server toate caracterele “+” sunt codate ” ” adica blank space. Nimeni nu ar observa acest lucru, mai ales ca nu apare nici un warning, nici o eroare pe device-ul IOS. Insa server-side, la decodificarea stringului base64 receptionat vom avea o eroare din cauza faptului ca nu poate fi scoasa imaginea de acolo.

Solutia?

Trebuie sa inlocuim in stringul base64 caracterul “+” cu “%2b” inainte de a face request-ul catre serverul web:

paramsStr = paramsStr.replacingOccurrences(of: "+", with: "%2b")

Share Button

prepare(for:sender:) – apelare de doua ori?

Sunt in urmatorul scenariu: un buton din ViewController-ul A – VCA este legat prin segue cu prezentare modala de un ViewController B – VCB.

In VCA am definit metoda prepare(for:sender:)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "asSegue" {
.....
}

Practic definesc ce anume fac atunci cand se va produce click pe butonul ce va aduce modalul VCB.

Din exces de zel fac urmatoarea greseala: In VCA, deci in ViewControllerul mama spun ca atunci cand se da click pe butonul care are atasat segue sa activez urmatoarea metoda:
performSegue(withIdentifier: "visibilitiesSegue", sender: self)

Totul merge bine si frumos, la click pe butonul din VCA apare modalul VCB, imi fac treaba si e perfect! Numai ca in consola am un pic warning:
Warning: Attempt to present VCB on VCA which is already presenting (null)
Nu m-ar deranja prea tare daca nu as vrea sa am consola curata de warning-uri.

Caut la Apple despre performSegue si aflu ca
“Normally, segues are initiated automatically and not using this method.”

Ce gresesc?

Metoda prepare(for:sender:) este apelata de doua ori, o data automat atunci cand eu dau click si se produce implicit prezentarea lui VCB si a doua oara cand eu i-am specificat faptul ca la click de buton trebuie sa activeze performSegue(withIdentifier:sender:)

Solutia?

Elimin de la executie performSegue(withIdentifier:sender:) deoarece atunci cand se face click pe buton pentru a prezenta VCB automat este executata prepare(for:sender:)

Share Button

Poate fi atasat un UIGestureRecognizer la mai multe view-uri?

Cei de la Apple spun ca nu: un UIGestureRecognizer poate fi utilizat doar cu un singur view.
Cum trecem peste acest impediment, avand o lista de view-uri, fiecare din aceste view-uri facand aceeasi actiune, sa zicem tiparirea campului tag?

1. generam o functie care va returna un UIGestureRecognizer

func setGestureRecognizer() -> UITapGestureRecognizer {
var gesture = UITapGestureRecognizer()
gesture = UITapGestureRecognizer(target: self, action: #selector(changeImage(sender:)))
return gesture
}

2. definim functia de raspuns in care extragem view-ul senderul UIImageView
func changeImage(sender: UITapGestureRecognizer) {
let tappedImageView = sender.view! as? UIImageView
print("button clicked - \(tappedImageView!.tag)")
}

3. folosim recognizer-ul in view-urile noastre
view1.addGestureRecognizer(setGestureRecognizer())
view2.addGestureRecognizer(setGestureRecognizer())
view3.addGestureRecognizer(setGestureRecognizer())

Share Button