How to simplify long subscription into multiple observers/drivers - swift3

How can I transform this (simplified for clarity, if you can believe it)…
override func viewDidLoad() {
super.viewDidLoad()
viewModel.track
.asObservable()
.subscribe(onNext: { upcomingTrack in
self.showLoadingView()
upcomingTrack.fetchYoutubeData()
.observeOn(ConcurrentDispatchQueueScheduler(globalConcurrentQueueQOS: .background))
.subscribe(onNext: { metadata in
if metadata.isInvalid {
viewModel.skip(upcomingTrack)
return
}
XCDYouTubeClient.default().getVideoWithIdentifier(metadata.youTubeId, completionHandler: { (video, error) in
if video != nil {
if let streamUrls = video?.streamURLs,
let streamUrl = streamUrls[NSNumber(value: XCDYouTubeVideoQuality.HD720.rawValue)]
?? streamUrls[NSNumber(value: XCDYouTubeVideoQuality.medium360.rawValue)] {
// πŸƒ DO STUFF WITH streamURL
// πŸƒ DO STUFF WITH upcomingTrack
self.hideLoadingView()
} else {
// ☠️ Not happy about this duplication
viewModel.skip(upcomingTrack)
}
} else {
viewModel.skip(upcomingTrack)
}
})
}).addDisposableTo(self.disposeBag)
}).addDisposableTo(self.disposeBag)
}
…into something like this:
override func viewDidLoad() {
super.viewDidLoad()
viewModel.trackBeganLoading
.drive(onNext: { _ in
self.showLoadingView()
}
viewModel.loadedTrack
.drive(onNext: { upcomingTrack in
// πŸƒ DO STUFF WITH upcomingTrack
self.hideLoadingView()
})
.addDisposableTo(disposeBag)
viewModel.streamURL
.drive(onNext: { url in
// πŸƒ DO STUFF WITH streamURL
})
.addDisposableTo(disposeBag)
}
Apologies in advance for the open-ended nature of this question. I'm not even sure where to start unravelling this beast!

Related

CoreData adding entity

Here is how I add new entity.
func addCountry(name: String, code: String, flagImageUri: String?, wikiDataId: String) {
let newCountry = CountryEntity(context: container.viewContext)
newCountry.name = name
newCountry.code = code
newCountry.flagImageUri = flagImageUri
newCountry.wikiDataId = wikiDataId
save()
}
Here is my data:
However when I use the add function in my view, I got this error:
CoreData: error: +[CountryEntity entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
And this is my button:
Button(action: {
country.isFaved = !country.isFaved
coreDataModel.addCountry(name: country.name, code: country.code, flagImageUri: country.flagImageUri, wikiDataId: country.wikiDataId)
}) {
Image(systemName: "star.fill")
.foregroundColor(country.isFaved ? .black : .white)
.scaledToFit()
}
This is the whole class. I'm fetching, saving ,adding and deleting all data here. I did everything like the video I watched in youtube.
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "CountryCoreData")
#Published var savedCountries: [CountryEntity] = []
init() {
container.loadPersistentStores(completionHandler: { _, error in
if let error = error {
print("CoreData failed to load: \(error.localizedDescription)")
} else {
print("Successfully loaded")
}
})
}
func fetchCountries() -> [CountryEntity]? {
let request = NSFetchRequest<CountryEntity>(entityName: "CountryEntity")
do {
let fetchedCountries = try container.viewContext.fetch(request)
return fetchedCountries
} catch {
print("Something went wrong while data fetching \(error)")
return nil
}
}
func delete(code: String) {
guard let fetchedCountries = fetchCountries() else { return }
for country in fetchedCountries {
if country.code!.contains(code) {
container.viewContext.delete(country)
save()
}
}
}
func addCountry(name: String, code: String, flagImageUri: String?, wikiDataId: String) {
let newCountry = CountryEntity(context: container.viewContext)
print("OSMAN")
newCountry.name = name
newCountry.code = code
newCountry.flagImageUri = flagImageUri
newCountry.wikiDataId = wikiDataId
save()
}
func save() {
do {
try container.viewContext.save()
fetchCountries()
} catch {
print("Error while saving the data: \(error)")
}
}
}
How can I solve this problem?

My Image stop animation when I do a request in swift 3

I've got a UImage, that needs to rotate until my call to the server ends.
But my image rotates only one time and stops after that.
My code:
#IBOutlet var imgLoader: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos: .userInitiated).async {
self.getSalons()
}
self.launchLoaderDesign()
NotificationCenter.default.addObserver(forName:NSNotification.Name("LoadAllShowNotification"), object: nil, queue: nil, using: notificationFinish)
}
func getSalons() -> Void {
let api:ApiSvain = ApiSvain()
api.getHeartStrokeSalon()
api.getSalonForHomePage()
}
func launchLoaderDesign() -> Void {
var posImg:Int = 0
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: { () -> Void in
self.imgLoader.transform = self.imgLoader.transform.rotated(by: CGFloat(M_PI_2))
})
{ (finished) -> Void in
self.launchLoaderDesign()
}
}
func getSalonForHomePage(){
let url = "MY_URL"
Alamofire.request(url, method: .get).validate().responseJSON
{ response in
if (response.error == nil)
{
let json = JSON(response.result.value!)
for (index, element) in json
{
let show:Show = Show(json: element, index: index)
StaticData.arrayOfShow.append(show)
}
NotificationCenter.default.post(name:NSNotification.Name("LoadAllShowNotification"), object: nil, userInfo: nil)
}
else
{
print(response.error!)
}
}
}
My function getSalonForHomePage sends a notification, and when I catch it I use performSegue to move to my new page.
I think my problem came from my misunderstanding of multi-threading.
Ps: I am using alamofire 4, for send request to my server.

Swift 3: Convert PromiseKit deferred to RxSwift

I'm currently replacing PromiseKit with RxSwift, and need to convert my deferred promise to RxSwift.
Current implementation example in PromiseKit:
private var deferredDidLayout = Promise<()>.pending()
override func layoutSubviews() {
super.layoutSubviews()
self.deferredDidLayout.fulfill()
}
func setup() {
_ = self.didLayout().then {_ -> Void in
// Do my stuff only one time!
}
}
private func didLayout() -> Promise<()> {
return self.deferredDidLayout.promise
}
Current hack-implementation in RxSwift:
private let observableDidLayout = PublishSubject<Void>()
override func layoutSubviews() {
super.layoutSubviews()
self.observableDidLayout.onCompleted()
}
func setup() {
_ = self.observableDidLayout
.subscribe(onCompleted: { _ in
// Do my stuff only one time!
// Issue: Will be executed on every onCompleted() call
})
}
Thank you in regard!
PromiseKit: https://github.com/mxcl/PromiseKit
RxSwift: https://github.com/ReactiveX/RxSwift
I believe that 'Completable' is what you are looking for - https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md#creating-a-completable

How to save variable in closure to external variable?

I'm trying to create a custom PickerView that gets it's data from an API call to a web-server. The problem I'm having is saving the parsed data into an external variable so that the PickerView protocol methods can access it.
// API Call / Parsing using Alamofire + Unbox
static func makeApiCall(completionHandler: #escaping (CustomDataStructure) -> ()) {
Alamofire.request(webserverUrl, method: .get).responseObject { (response: DataResponse<Experiment>) in
switch response.result {
case .success:
if var configParams = response.result.value {
let inputConfigs = removeExtrasParams(experiment: response.result.value!)
let modifiedViewModel = modifyViewModel(experiment: &configParams, inputConfigs: inputConfigs)
completionHandler(modifiedViewModel)
}
case .failure(_):
break
}
}
}
// Custom PickerClass
class CustomPickerView: UIPickerView {
fileprivate var customDS: CustomDataStructure?
override init() {
super.init()
dataSource = self
delegate = self
SomeClass.makeApiCall(completionHandler: { customds in
self.customDS = customds
})
}
...
}
extension CustomPickerView: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if let customds = customDS {
if let customDSValues = customds.inputs.first?.value {
return customDSValues[row]
}
}
return "apple"
}
}
extension CustomPickerView: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if let customds = customDS {
return customds.inputs.values.count
} else {
return 0
}
}
}
The problem I'm having is that customDS returns nil everytime.
What am I doing wrong here?
In the completion block of makeApiCall simply reload your pickerView's component on main thread and you all set to go.
SomeClass.makeApiCall(completionHandler: { customds in
self.customDS = customds
DispatchQueue.main.async {
self.reloadComponent(0)
}
})

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()
}