Async await swift ui - ios

I have the following code, I would like to use async-await for the two calls.
At the moment inside the function I used a kind of check variable, which when both are set the code is executed.
How can I do?
....
.onAppear {
DispatchQueue.global(qos: .background).async {
getRemoteHead(url: repoUrlStr)
getRemoteBranch(url: repoUrlStr)
}
}
func getRemoteHead(url: String) {
do {
let branch = try Remote().getRemoteHEAD(url: url)
if branch[0].contains("fatal:") {
Log.warning("Error: getRemoteHead")
activeSheet = .error("Error: getRemoteHead")
} else {
self.mainBranch = branch[0]
self.selectedBranch = branch[0]
self.check += 1
if check == 2 {
check = 0
activeSheet = .select
}
}
} catch {
Log.error("Failed to find main branch name.")
}
}
func getRemoteBranch(url: String) {
do {
let branches = try Remote().getRemoteBranch(url: url)
if branches[0].contains("fatal:") {
Log.warning("Error: getRemoteBranch")
activeSheet = .error("Error: getRemoteBranch")
} else {
self.arrayBranch = branches
self.check += 1
if check == 2 {
check = 0
activeSheet = .select
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// Force a UI Update.
allBranches.toggle()
}
}
}
} catch {
Log.error("Failed to find branches.")
}
}

Related

Swift: Using try? to catch Index out of range

so i am making game and there is a grid and you can only place if any of the adjacent blocks are a source block, well if you are trying to place a block in the top grid there's no block above it so an Index out of range error will be thrown. I am trying to catch it but xcode seems to think no error is possible. Am i doing this wrong? I guess I could go to each case and add a catch but I feel like this should work.
copy friendly text if you would like to try it out in your own IDE:
func isValidPlacement(row:Int,col:Int) -> Bool {
if let t = try? tiles[row-1][col], t.isSource {
return true
}
else if let t = try? tiles[row][col-1], t.isSource {
return true
}
else if let t = try? tiles[row+1][col], t.isSource {
return true
}
else if let t = try? tiles[row][col+1], t.isSource {
return true
}
else {
return false
}
}
Runtime errors and returning bools is the Objective-C way! Throw throw throw! 🙀
public extension Collection {
/// - Returns: same as subscript, if index is in bounds
/// - Throws: CollectionIndexingError
func element(at index: Index) throws -> Element {
guard indices.contains(index)
else { throw CollectionIndexingError() }
return self[index]
}
}
extension Collection where Element: Collection {
/// - Returns: same as subscripting, if indices are in bounds
/// - Throws: CollectionIndexingError
func element( at indices: (Index, Element.Index) ) throws -> Element.Element {
try element(at: indices.0).element(at: indices.1)
}
}
/// Thrown when `getElement` is called with an invalid index.
public struct CollectionIndexingError: Error { }
extension Tile {
enum PlacementError: Error {
case invalid
case noSources
}
}
extension Collection where
Index == Int,
Element: Collection, Element.Index == Int, Element.Element == Tile
{
func validatePlacement(row: Index, column: Element.Index) throws {
let tiles = [(-1, 0), (0, -1), (1, 0), (0, 1)].compactMap {
try? element( at: (row + $0.0, column + $0.1) )
}
guard !tiles.isEmpty
else { throw Tile.PlacementError.invalid }
guard tiles.contains(where: \.isSource)
else { throw Tile.PlacementError.noSources }
}
}
In Swift. you can only catch instances of Error that are thrown by code, you can't catch runtime exceptions such as an array bounds violation
You can create your own safeAccess function. You don't say what type is in your array so I will use SomeType as an example
func safeAccess(row:Int, col:Int) -> SomeType? {
guard row >= 0, row < tiles.count else {
return nil
}
guard col >= 0, col < tiles[row].count else {
return nil
}
return tiles[row][col]
}
func isValidPlacement(row:Int,col:Int) -> Bool {
if let t = tiles.safeAccess(row:row-1,col:col), t.isSource {
return true
}
if let t = tiles.safeAccess(row:row,col:col-1),, t.isSource {
return true
}
if let t = tiles.safeAccess(row:row+1,col:col), t.isSource {
return true
}
if let t = tiles.safeAccess(row:row,col:col+1), t.isSource {
return true
}
return false
}
You could also define an extension on Array
extension Array {
func element(at index: Int) -> Element? {
if index >= 0 && index < self.count {
return self[index]
}
return nil
}
}
And to use it:
func isValidPlacement(row:Int,col:Int) -> Bool {
if let tiles = tiles.element(at:row-1), let t = tiles.element(at:col), t.isSource {
return true
}
else if tiles.element(at:row), let t = tiles.element(at:col-1), t.isSource {
return true
}
else if let tiles = tiles.element(at:row+1), let t = tiles.element(at:col), t.isSource {
return true
}
else if let tiles = tiles.element(at:row), let t = tiles.element(at:col+1), t.isSource {
return true
}
else {
return false
}
}
Accessing an index of an array which doesn't exist results in a run time exception. You can't use try here because the array is not throwing anything.
Try adding these two lines at the start of the func and get rid of all of try?
guard row <= tiles.count - 1 else {
return false
}
guard col <= tiles[row].count - 1 else {
return false
}
Also i am assuming, whatever the type array stores here is an optional. if not you can remove if lets too
func isValidPlacement(row:Int,col:Int) -> Bool {
guard row <= tiles.count - 1 else {
return false
}
guard col <= tiles[row].count - 1 else {
return false
}
if let t = tiles[row-1][col], t.isSource {
return true
}
else if let t = tiles[row][col-1], t.isSource {
return true
}
else if let t = tiles[row+1][col], t.isSource {
return true
}
else if let t = tiles[row][col+1], t.isSource {
return true
}
else {
return false
}
}

Vapor: route return before all modification done

I have following route:
router.post([Page].self, at: "/fetchStatusOfManagedReleases") { (req, pages) -> Future<[Page]> in
let eventIds = pages.map { $0.events }.flatMap { $0 }.map { $0.id }
return Release.query(on: req).filter(\.fbId ~~ eventIds).all().map { releases in
var result: [Page] = []
for p in pages {
let page = p
var pageEvents: [Event] = []
for e in p.events {
let event = e
if let release = releases.first(where: { $0.fbId == e.id }) {
event.inProgress = release.inProgress
event.purpose = release.purpose
_ = try release.testPrices.query(on:req).all().map { testP in
event.testPrices = testP // <--- this line is not applied
}
} else {
event.inProgress = false
}
pageEvents.append(event)
}
page.events = pageEvents
result.append(page)
}
return result
}
}
Unfortunatelly event.testPrices = testP is not applied, it will e not part of the response. What can I do? At some cases I do not need to postpone "return". How can I dissolve scheduling issue?
I do a ~~ operation on TestPrice also as for Release before.
router.post([Page].self, at: "/fetchStatusOfManagedReleases") { (req, pages) -> Future<[Page]> in
let eventIds = pages.map { $0.events }.flatMap { $0 }.map { $0.id }
return Release.query(on: req).filter(\.fbId ~~ eventIds).all().flatMap { releases in
let releaseInnerIds = releases.map {$0.id}
return TestPrice.query(on: req).filter(\.id ~~ releaseInnerIds).all().map { testPrices in
var result: [Page] = []
for p in pages {
let page = p
var pageEvents: [Event] = []
for e in p.events {
let event = e
if let release = releases.first(where: { $0.fbId == e.id }) {
event.inProgress = release.inProgress
event.purpose = release.purpose
event.testPrices = testPrices.compactMap({testPrice in
if testPrice.release.parentID == release.id {
return testPrice
} else {
return nil
}
})
} else {
event.inProgress = false
}
pageEvents.append(event)
}
page.events = pageEvents
result.append(page)
}
return result
}
}
}

Swift how to wait until a value changes

I am writing an application which tests your ability in Classical Greek. And I have a few View Controllers. In the test view controller, I have a huge Begin button, which when pressed initiates a sequence of code, as follows:
#IBAction func test(_ sender: Any) {
beginBtn.isHidden = true
beginBtn.isEnabled = false
answerOne.isHidden = false
answerTwo.isHidden = false
answerThree.isHidden = false
answerFour.isHidden = false
data.currentNumOQue = (data.qCQ + data.qWQ + data.qSQ)
if data.chooseCAlertDataLoaded == false {
data.chooseCharacterQuestionType.addAction(data.chooseCharacterQuestionTypeEngGrk)
data.chooseCharacterQuestionType.addAction(data.chooseCharacterQuestionTypeGrkEng)
data.chooseCAlertDataLoaded = true
} else {
print("not first question")
}
while data.currentQC <= data.qCQ {
present(data.chooseCharacterQuestionType, animated: false, completion: nil)
DispatchQueue.global(qos: .background).async {
while data.chooseCAlertReturnsEngGrk == nil {
}
DispatchQueue.main.sync {
if data.chooseCAlertReturnsEngGrk == true {
//Eng-Grk QUestion
data.chooseCAlertReturnsEngGrk = nil
} else {
//Grk=Eng QUestion
data.chooseCAlertReturnsEngGrk = nil
}
}
}
data.currentQC += 1
data.currentQ += 1
}
data.currentQC = 1
data.currentQW = 1
data.currentQS = 1
}
Can anyone help me in how to wait until the value chooseCAlertReturnsEngGrk is not nil and execute on, but not 'freezing' the UI when doing so?
Override didSet to call some function.. then in that callback function, do whatever you want with the parameter..
class DataSomething {
var chooseCAlertReturnsEngGrkObserver: ((_ newValue: SomeTime?) -> Void)?
var chooseCAlertReturnsEngGrk: SomeType? = nil {
didSet {
chooseCAlertReturnsEngGrkObserver?(chooseCAlertReturnsEngGrk)
}
}
}

Completion block never gets called?

We have a function that when it finishes another function should be called in it's completion block but whatever is inside the completion block is never called. Here is the function:
func appendAllData (completion: () -> Void) {
guard let movieDetails = self.movieDetailsData else {
// handle nil case
return;
}
if let posterImage = self.movieDetailsData?.poster {
self.posterArray.append(posterImage)
}
if let overview = self.movieDetailsData?.overview {
self.overviewArray.append(overview)
}
if let releaseDate = self.movieDetailsData?.releaseData {
self.releaseInfoArray.append(releaseDate)
}
if let runtime = self.movieDetailsData?.runtime {
self.releaseInfoArray.append(String(describing: runtime))
}
if let genre = self.movieDetailsData?.genre {
if !genre.isEmpty {
self.releaseInfoArray.append(genre[0].name)
}
}
if let budget = self.movieDetailsData?.budget {
self.boxOfficeArray.append(budget)
}
if let revenue = self.movieDetailsData?.revenue {
self.boxOfficeArray.append(revenue)
}
if let homepage = self.movieDetailsData?.homepage {
self.homePageArray.append(homepage)
}
if let images = self.movieDetailsData?.images {
self.imageArray += images.backdropImages.map{ $0.filePath }
}
}
Here is how it's used:
self.appendAllData(completion: { _ in
//Nothing inside here gets called
DispatchQueue.main.async {
print(self.movieDetailsData?.poster )
if let bgImage = self.movieDetailsData?.poster {
self.backgroundImage.sd_setImage(with: URL(string:"\(baseImageURL)\(bgImage)"))
print("background pic loaded")
self.backgroundImage.addBlurEffect()
}
self.detailTableView.reloadData()
}
})
Nothing inside the completion block is called, any idea how to fix this?
I believe you need to call the completion() at the end for it execute your completion code.
func appendAllData (completion: () -> Void) {
guard let movieDetails = self.movieDetailsData else {
// handle nil case
return;
}
if let posterImage = self.movieDetailsData?.poster {
self.posterArray.append(posterImage)
}
if let overview = self.movieDetailsData?.overview {
self.overviewArray.append(overview)
}
if let releaseDate = self.movieDetailsData?.releaseData {
self.releaseInfoArray.append(releaseDate)
}
if let runtime = self.movieDetailsData?.runtime {
self.releaseInfoArray.append(String(describing: runtime))
}
if let genre = self.movieDetailsData?.genre {
if !genre.isEmpty {
self.releaseInfoArray.append(genre[0].name)
}
}
if let budget = self.movieDetailsData?.budget {
self.boxOfficeArray.append(budget)
}
if let revenue = self.movieDetailsData?.revenue {
self.boxOfficeArray.append(revenue)
}
if let homepage = self.movieDetailsData?.homepage {
self.homePageArray.append(homepage)
}
if let images = self.movieDetailsData?.images {
self.imageArray += images.backdropImages.map{ $0.filePath }
}
completion()
}

How do I know that the contacts have been modified, if the application was not active on iOS?

I need to run a function when the contacts have changed. If the application is active, you can do this with NotificationCenter as narrated in this post (sometimes It works when I add a new number to an existing contact). How do I know that the contact (or contacts) have been changed after the launch of the application?
I made the following functions for my task
#objc private func matchingContacts() {
if isSuccessContactUploading {
contactManager.matchingContacts(notMatch: { [weak self] in
guard let _self = self else { return }
debugPrint("matchingContacts != equals")
_self.isSuccessContactUploading = false
_self.syncContacts()
})
}
}
These functions are in ContactManager
func matchingContacts(notMatch: (() -> Void)?) {
getContacts { (contacts, error) in
if error == nil {
debugPrint("contacts count", contacts.count)
self.getContactsDictionaryFromCache(contacts, notMatch: {
notMatch?()
})
}
}
}
private func getContactsDictionaryFromCache(_ contacts: [CNContact], notMatch: (() -> Void)?) {
var isMatching = true
for contact in contacts {
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
if let contactDictionary = cache[key] {
if !contactDictionary.isEqual(to: contact.dictionary) {
debugPrint("contactDictionary not matching")
isMatching = false
}
} else {
debugPrint("contactDictionary isn't here")
isMatching = false
}
} catch {
debugPrint(error.localizedDescription)
isMatching = false
}
}
if !isMatching {
notMatch?()
}
cacheContacts(contacts)
}
private func cacheContacts(_ contacts: [CNContact]) {
for contact in contacts {
let contactDictionary = contact.dictionary as NSDictionary
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
cache[key] = contactDictionary
} catch {
debugPrint(error.localizedDescription)
}
}
}

Resources