rac_signalForControlEvents not signaling in a UICollectionViewCell - ios

I have a bunch of UICollectionViewCells containing buttons. For some reason, my signal refuses to fire when a button is inside of a UICollectionViewCell. Switching to the normal addTarget:action:forControlEvents: will work, but not the RAC signal. I've had this happen in 2 different collection views, and 2 different custom collection cells.
All I'm doing is:
[[cell.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
// code to be executed here, which doesn't happen
}];
What am I missing?

try:
[[[cell.button rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(id x) {
// code to be executed here, which doesn't happen
}];

I don't have enough reputation to comment so I will just comment here. It seems that there is something wrong with the button instance that is causing the signal not to fire. How are your buttons instantiated and where in the tableView:cellForRow:atIndexPath are you subscribing to the signal.

Related

Reactive cocoa: Stop a RACSignal after UIViewController is dealloc

In my case, I presented a containerViewController consists of several UIViewControllers.
One of them, controller A, will send request to server every 10 seconds to get data. I used a RACSignal to do it:
[[[RACSignal interval:10 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:self.rac_willDeallocSignal] subscribeNext:DoSomeThing];
But when the containerViewController is dismissed from the rootViewController, the signal still fired every 10 seconds, means rac_willDeallocSignal of controller A is not called. How can it be fixed?????
Thanks!!!
Maybe it is too late to answer this question, but yet it may be helpful for those who are looking for solution.
I have solved similar problem with creating separate signal when UIViewController will disappear, and used that signal as takeUntil: in the Interval signal. Code looks like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
RACSignal *rac_viewWillDisappear =
[self rac_signalForSelector:#selector(viewWillDisappear:)];
[[[RACSignal interval:10 onScheduler:[RACScheduler mainThreadScheduler]]
takeUntil:rac_viewWillDisappear] subscribeNext:^(id x) {
//Do what you need
}];
}
The interval signal is infinite, it will never complete. Consequently, any objects that are strongly captured in the subscription blocks will also live on indefinitely, and thus their willDeallocSignal will not do anything. There are two ways to work around this:
Capture objects weakly
Explicitly dispose of the subscription
The first case is preferable. In this case, you could use #weakify(self) outside of the block and #strongify(self) inside the block.
The second option is more of a clumsy brute approach. I wouldn't recommend it.
See RAC's Memory Management.

How to refresh UIView

I get one problem is that,
This's showads function.
[self.mMainView addSubview:adViewController.view];
//...(my function to display ads)
This's hideads function.
//...(my function to hide ads)
[[AdViewController sharedAdViewController].view removeFromSuperview];
[self.mMainView setNeedsDisplay];
The mMainView is a UIView.
The AdViewController is a UIViewController.
The problems is that after calling showads fucntion, the my ads display on mMainView. But after calling hideads function, the my ads don't disappear, it still appear on mMainView.
Note: After calling hideads and make an interrupt then resume the app, the my ads will disappear.
So, i want to remove it, could you please explain a bit in more detail and show me how to fix it if you can pls ?
You don't need to call setNeedsDisplay when removing or adding subviews. The fact that it updates after an interrupt makes me suspect that you're not calling [[AdViewController sharedAdViewController].view removeFromSuperview]; on the main thread. What is the context of that code?
If it's not on the main thread, you can schedule on it:
dispatch_async(dispatch_get_main_queue(), ^{
[[AdViewController sharedAdViewController].view removeFromSuperview];
});

setNeedsDisplay is submitted on main queue but is not called

The method below is called on a non-main thread, to be specific, in a recording audio queue callback
- (void)myMethod
{
//...
dispatch_async(dispatch_get_main_queue(), ^{
[myGraphView setNeedsDisplayInRect:CGRectMake(a, b, c, d)];
NSLog(#"Block called");
});
//...
}
where myGraphView is a custom UIView object. For what I know, setNeedsDisplayInRect: should be called on main thread which is why I have dispatch_async... in place. Now the problem is the method - (void)drawRect:(CGRect)rect I implemented for myGraph is never called even though the NSLog in the block has been called for many times.
There are a few possibilities here.
From the Class Reference:
Note: If your view is backed by a CAEAGLLayer object, this method has
no effect. It is intended for use only with views that use native
drawing technologies (such as UIKit and Core Graphics) to render their
content.
The other option, which is probably the cause in this case, has to do with the actual geometry. If the provided rectangle is invalid or off screen, the call does nothing. I would suggest you verify the that the rectangle is being calculated as it should be.
Thanks to #Neal's answer which led me to find out that myGraphView was, after it had been alloc-inited the first time, alloc-inited again. However, unlike the first alloc-init after which I added myGraphView to its superview, I forgot to do so after the second alloc-init.
The lesson I've learned here is that when a view is not doing what it's expected to do, such as not being displayed or updated, always check this third possibility where you forget to add it back to its superview after it's got alloc-inited again somewhere in your code. Also, if the view has a delegate you would tend to forget to set it as well.

Make UIView Appear Before Network Operation

I have a seemingly simple problem that I cannot for the life of me seem to figure out. In my iOS App, I have a UICollectionView that triggers network operation upon tapping it that can take a few seconds to complete. While the information is being downloaded, I want to display a UIView that fills the cell with a UIActivityIndicatorView that sits in the square until the loading is done, and the segue triggered. The problem is that it never appears. Right now my code looks like:
myLoadView.hidden = NO;
//Network Operation
myLoadView.hidden = YES;
The App simply stops for a couple seconds, and then moves on the the next view. I'd imagine Grand Central Dispatch has somthing to do with the solution, however please keep in mind that this code takes place in prepareForSegue, and the network info needs to be passed to the next View. For this reason not finishing the download before switching scenes has an obvious problem. Any help would be VASTLY appreciated. Thanks!
iOS commits changes in the interfaces after working out a routine. Hence you should perform your network operation in a background thread and then get back back on the main and perform the "show my view now thing". Have a look the below code for reference.
myLoadView.hidden = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Network Operation
dispatch_async(dispatch_get_main_queue(), ^{
myLoadView.hidden = YES;
});
});
Your network operation seems to be carried out on the main thread, aka UI thread. This blocks all further UI calls, including the call to unhide a view, until completion.
To resolve this, make your call asynchronous.
You should read this in full, if you haven't already.
As mentioned by other answers, the problem is that the UIView change doesn't happen until the current method finishes running, which is where you are blocking. Before GCD was available I would split methods in two and use performSelector:withObject:afterDelay (to run the second part also on the UI loop) or performSelectorInBackground:withObject: at the end of the first method. This would commit all the waiting animaations first, then do the actual tasks in the second method.
Well the better option for this type of indication is by using the custom HUD libraries like SVProgressHUD or MBProgressHUD

UITableView's reloadData sometimes does not call numberOfRowsInSection

[tableView reloadData] does not call numberOfRowsInSection:(NSInteger)section
What i would write as an answer in stackoverflow if i saw a question like this:
Be sure that delegate and dataSource is connected perfectly.
Just in case check it if you are calling it from the main thread or not.
I tried my answers before asking the question.
I put the following line in my MyTableView class (extended from UITableView)
- (void)reloadData{
NSLog(#"MyTableView (reloadData) self.dataSource: %#, delegate: %#, isMainThread: %d",self.dataSource,self.delegate,[NSThread isMainThread]);
[super reloadData];
[self.delegate performSelector:#selector(tableReloaded:) withObject:self];
}
I see that it is called from mainthread and datasource and delegate is never nil.
I am not curious about why tableView does not call cellForRow as it first has to call numberOfRows to see if the result > 0. However, it doesn't call numberOfRows too...
Any ideas? What can be the reason for tableView to giveUp calling numberOfRows function?
Update:
I've put new lines to reloadData function to print FullStack to see if it is being called from tableView's own functions. The result: They are called outside of the tableView so it there shouldn't be any unexpected behaviour.
Update2:
"Sometimes": I have discovered this problem after my users started to say that "sometimes" they don't see the changes in the table. After that, i tried to hit run button in XCode continuouslly until the app opens with this bug. (30% percentage of my runs shows this bug). When it happens, until i restart the application, reloadData never works again.
Update3:
I put self.dataSource==myExpectedViewController and also [self.dataSource tableView:self numberOfRowsInSection:0] to see if even delegates are not nil maybe they were being cloned etc.. The result was True and numberOfRows were correctly returning>0. So delegates are correct and i have more items than zero.
Update4:
I tried it with a fresh UITableView (removing my custom UITableView), i got the same results.
Update5:
I've put a button on the screen which recreates the table and sets its delegates. Whenever the problem in the question happens, i hit this button and everything starts to work perfectly. So, there must be something that breaks internals of UITableView which invalidates every call to reloadData, but i still couldn't find it.
Edited : I tried to replicate the issue and found, when I presented another viewcontroller on current view controller which has this tableview. and then 1. tried to reload this tableview with some data and 2. dismissed the viewcontroller which is on top of my currentviewcontroller with animation, simultaneously , then since in this duration, table view is not visible so it won't be reloaded.
To resolve, you can put a delay there or use completion block of that animation to reload table with effect.
Otherwise problem can be related to thread, UIElements are expected to be updated only on main thread.
During API calls and other process, we enters in other thread, and mostly we ignore to check whether we are on main thread before updating UIElement.
You should call yourTable.reloadData() in main thread using DispatchQueue.main.async{yourTable.reloadData()} and make sure it is being called on main thread.

Resources