I know this seems like a simple question but if I #import <UIKit/UIKit.h> into a class file is this breaking the MVC rule?
The reason I ask is I set up a class that has a function to make an API call. Inside of that API function I want to use the dispatch_async method to get the data back to the main thread and want to call [tableView reload] inside of that dispatch_async method.
To do this I need to have access to the UITableView class so when I call my function I can pass in my tableView. Sorry still somewhat new to all of this.
Thanks!
First of all, MVC is a design pattern, and not a set of rules. It is a way of organizing code so that the view (and how to represent it) is totally decoupled from the model/application.
Secondly, you are correct in saying that importing the viewController file in the class that makes the API calls (let's call it API Class) is against the principles of MVC.
But you can make the view update in the viewController class itself! Instead of importing the ViewController class into the API class, you can do the absolute reverse and use the API class from the View Controller class. There are a lot of ways that classes can communicate with each other while conforming to the MVC pattern. Have a look at this article on the website objc.io which talks about the same.
Have a look at AFNetworking, an extremely popular networking library for applications written in Objective-C. It makes excellent use of objective-c blocks, and the same is recommended for your use case as well.
For example, your class for making the API call can look like this:
//APICall.h
#interface APICall : NSObject
-(void)makeAPICallWithHandler:(void(^)(NSError*, id data))handler;
#end
//APICall.m
#import "APICall.h"
#implementation APICall
-(void)makeAPICallWithHandler:(void(^)(NSError*, id data))handler
{
NSError *err;
id data;
//Make your API call and then pass the result in the handler
if (err)
{
handler(err, nil);
return;
}
handler(nil, data);
}
#end
The above method can be implemented by the view controller as follows:
//ViewController.m
#import "ViewController.h"
#import "APIClass.h"
#implementation ViewController
{
APIClass *api;
id displayData;
__weak IBOutlet UITableView *tableView;
}
-(void)viewDidLoad
{
[super viewDidLoad];
api = [APICall new];
[api makeAPICallWithHandler:^(NSError* error, id data) {
if (error)
{
//Show alert or something
return;
}
displayData = data;
[tableView reloadData];
}];
}
#end
The point is that using blocks, you can make the data from the API call available to the viewController which can then update the tableView and maintain it's data source. This conforms to MVC as you will have totally decoupled the view update from the actual API call.
Related
#import "ViewController.h"
#interface ViewController ()
//Declare block as property
#property (nonatomic, strong) void (^dataBlock)(BOOL success);
#end
#implementation ViewController
- (void) myMethod1:(void (^)(BOOL success))response {
//Here data block holds the reference to response block
_dataBlock = response;
}
- (void) myMethod2 {
//Check for _dataBlock and invoke it.
if (_dataBlock) {
_dataBlock(YES);
}
}
- (IBAction) buttonClick {
//Call for myMethod1 and call back block is invoked in myMethod2
[self myMethod1:^(BOOL success) {
if (success) {
NSLog(#"Im Done");
}
}];
}
#end
Above sample is my code in Objective-C
Callback Block of "myMethod1"(response) is having reference/stored to "dataBlock" Property.
then invoke "dataBlock" from "myMethod2".
since "datablock" have reference to"myMethod1" block named "response", i'll be getting call back in "myMethod1", please look at the code snippet (similar to function to pointer).
same thing i want to implement in swift. I have tried implementing this in swift using closures, but not getting it.
No, there is not, unless it's on a Jailbroken device.
Apple does not let 3rd party apps alter the core behavior of the phone.
Now you could put the phone in a Faraday cage and put antennas on the inside and outside, and disconnect them when you wanted to block calls.
Actually you sort of can, but not really programmatically from iOS. If the BLE device implements the HID profile then you can simulate a double click on the lock button which would dismiss the call. I have done that, but it is a bit of a clunky solution.
A weird situation arises while using delegates in iOS.
A delegate method send its control to different class rather than calling class implemented same delegate method.
Example :
I am having a Dashboard which is calling a web service (a different class implemented all web service calls). In dashboard global web service class object exists with delegate self. When we call a web service the control comes to this class. So thats fine.
Now i am having another class which got push from dashboard or some time from other view controllers as well and it is also calling same web service. It is having its own web service class object globally and set delegate to self.
When I am calling web service from this class with its web service object after web service call control goes to delegate of dashboard not to this class though call has been made from this class and delegation is set to self.
This situation arises to many place where pushed child also implemented the same delegate method which its previous class has also implemented and thus instead delegation called of pushed class the method is called of the previous class.
Here is the main lines of code explaining above example :
WebServiceCaller.h
Class handles all web services caller
#import <Foundation/Foundation.h>
#import "WebserviceEnum.h"
#protocol WebServiceCallerDelegate;
#interface WebServiceCaller : NSObject
{
__unsafe_unretained id <WebServiceCallerDelegate> delegate;
NSOperationQueue * operationQueue;
}
#property(nonatomic, assign) id <WebServiceCallerDelegate> delegate;
-(void)cancelWebserviceCall;
-(void)cancelAllCalls;
#pragma mark - Class Methods
-(void)getListOfAnalyticQuestionsOfUserID:(NSString*)userid;
#end
#pragma mark - Protocol Methods
#protocol WebServiceCallerDelegate <NSObject>
#optional
-(void)getListOfAnalyticQuestionsCompletesSuccessfully:(BOOL)success WithList:(NSMutableArray*)arrQuestions WithMessage:(NSString*)message;
#end
WebServiceCaller.m
When after webservice call result will be sent to calling class.
-(void) getListOfAnalyticQuestionsOfUserID:(NSString*)userid{
NSString *url = [NSString stringWithFormat:#"%#?user_id=%#",[self getURLWithBase:BaseURL relativeURL:#"wt_question.php"],userid];
//webservice call goes here and after completion calls below method
}
-(void)getListOfAnalyticQuestionsCalledSuccessfully:(BOOL)success WithData:(id)Data WithMessage:(NSString*)errorMessage{
NSDictionary *dictResult = (NSDictionary*)Data;
NSMutableArray *arrAnalytics = nil;
NSString *message = errorMessage;
if (success) {
arrAnalytics = [dictResult objectForKey:#"items"];
message = SuccessMessage;
}
else{
if (message.length==0) {
message = NETWORKERRORMESSAGE;
}
}
if(self.delegate!=nil && [(id)[self delegate] respondsToSelector:#selector(getListOfAnalyticQuestionsCompletesSuccessfully:WithList:WithMessage:)])
{
[(id)[self delegate] getListOfAnalyticQuestionsCompletesSuccessfully:success WithList:arrAnalytics WithMessage:message];
}
}
Code snippets from calling class suppose class B, same implementation has been done in class A also from which B has been pushed so every thing is working fine instead of sending control to class B control is sent to class A (webServiceCaller object is created in class A in same manner as in class B and calling same web service, This is just an example same thing happened for other web services too. which is implemented by both pushed and its previous class.)
#import "WebServiceCaller.h"
#define numberOfRecordsPerPage 15
#interface AnalyisViewController ()<UITableViewDataSource,UITableViewDelegate,WebServiceCallerDelegate>
{
NSInteger currentIndex;
NSDictionary *dictQuesList;
NSInteger totalPages;
NSMutableArray *arrQuestionList;
WebServiceCaller *webServiceCaller;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
webServiceCaller = [[WebServiceCaller alloc]init];
webServiceCaller.delegate = self;
[self showQuestions];
}
-(void)showQuestions{
[ProgressHUD show:#"Loading..." Interaction:NO];
[webServiceCaller cancelAllCalls];
[webServiceCaller getListOfAnalyticQuestionsOfUserID:userid];
}
#pragma mark - Webservice delegate
-(void) getListOfAnalyticQuestionsCompletesSuccessfully:(BOOL)success WithList:(NSMutableArray*)arrQuestions WithMessage:(NSString*)message{
//Handles my stuff
}
#end
Any idea of this weird situation.
I'm using #implementation to add a new function to UIView.
#implementation UIView (test)
- (void)newFunction {
}
#end
Now, in the newFunction I want to "grab" the object (UIView) that was used when calling the function.
For example when I call newFunction within viewDidLoad
- (void)viewDidLoad {
[myView newFunction];
}
I want the newFunction to know what object was used to make the call (in this case, myView).
A simple solution would be to pass it along when making the call ([myView newFunction:myView]), but that is not what I am looking for.
I looked at Apple's documentation on the subject, but didn't really find an answer to my question.
#import "UIView+UIView_Category.h"
#implementation UIView (UIView_Category)
- (void)newFunction
{
NSLog(#"Object = %#",self);
}
#end
What you describe is called a category (not #implementation). It is an extension to the UIView class (in this case).
Generalcally:
#implementation __CLASS_TO_EXTEND__ (__CATEGORY_NAME__)
The category, as it is an extension, is the instance that you call the method on. So, you use self as you usually would to access the current instance.
Okay, so I was reading here declaring global variables in iPhone project and I noticed the line with this code: [[[UIApplication sharedApplication] delegate] myNSString];.
Basically, I want a user to input something in a text field on the flipside view, then have it stored in a variable which is accessed on the main view. Ideally, the main view would be able to just read the text field from the flipside view, but this seems to be impossible (I've spent several hours each day for the past few days scouring the web and various books for an answer about how to do this and no one seems to be able to give a definitive answer). Therefore, I'm resorting to using a global variable to tackle this.
Will the code that I printed above somehow allow me to do this? I've been trying to adapt it for the past hour, but have come up with nothing except No known instance method for selector 'myNSString' and I'm not quite sure what that means in this case.
Can someone please help me out? I feel like I can keep trying different things but without some sort of help, I'm just shooting in the dark here. Thank you!
You may want to think about using a singleton to hold your data if you're set on using a global variable. There's a good tutorial on singletons here: http://www.galloway.me.uk/tutorials/singleton-classes/ -basically it's a class that can be shared throughout the application and accessed/modified by different controllers. You'd be able to create a property on it, write to that property from the flip view, and then access that property from your main view.
#import "Singleton.h"
#implementation Singleton
#synthesize yourTextField;
#pragma mark Singleton Methods
+ (id)sharedManager {
static Singleton *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
yourTextField = #"";
}
return self;
}
You could call it in code by importing its header file and:
Singleton *mySingleton = [Singleton sharedManager];
the mySingleton object will have the text field attached. It can be accessed by:
mySingleton.yourTextField;
.h file:
#import <Foundation/Foundation.h>
#interface Singleton : NSObject
#property (nonatomic, strong) NSString *yourTextField;
+ (id)sharedManager;
#end
Singleton (remember about dispatch_once), static variables or NSUserDefaults. It really depends what you really need.
If you are using storyboards and just want to pass data between VC, then you can use "prepareForSegue" method (described here https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instm/UIViewController/prepareForSegue:sender:).
Segue has "destinationController" property, so you can setup VC before showing it.
I have a question about passing data in iOS/Objective-C. I just started creating an app that connects and fetches data from a website, and now I'm having a problem.
My default and root view controller is called ViewController. It has the basic login UI views: 2 textfields and a button.
When the button has been clicked/touched, the ViewController calls a method from another class called LoginService. Now LoginService handles the connection to the website. I have no problem connecting to the website and fetching data from it, but I have a problem returning the fetched data, now processed as an NSDictionary, to the ViewController.
The first thing I tried was to create a setter method in the ViewController that sets the instance variable userProfile to the NSDictionary passed into it. It failed, however. I tried using it in the NSURLConnectionDataDelegate method connectionDidFinishLoading from the LoginService.
This might be a silly question, but I have no idea how can I pass the fetched NSDictionary from LoginService to the ViewController after the button is clicked. Do I need blocks, queue, or something else? I mean, for example, I need to set a label below my login button to the name of the user who logged in. How can I perform this?
Hope someone can help me. I'd greatly appreciate it.
As danh has explained blocks pattern for doing this, I will try to explain the delegating pattern. The steps for making this work:
In LoginService.h
Create a protocol definition in your LoginService like this:
#protocol LoginServiceDelegate
-(void)applicationLoggedIn:(NSMutableDictionary*) responseData;
#end
Now add a member pointer holding this delegate and add a property for this
#interface LoginService {
id<LoginServiceDelegate>delegate;
}
#property (nonatomic, assign) id <LoginServiceDelegate> delegate;
In LoginService.m
Once you got the response for login in connectionDidFinishLoading, just invoke the delegate method like below:
if ([delegate respondsToSelector:#selector(applicationLoggedIn:)]) {
[delegate applicationLoggedIn:responseDict];
}
In LoginViewController.h
Now to use this in your LoginViewController, you need to implement this protocol
#import "LoginService.h"
#interface LoginViewController<LoginServiceDelegate>
In LoginViewController.m
Assign the delegate of LoginService to LoginViewController
LoginService* loginService = [[LoginService alloc]init];
loginService.delegate = self;
Implement the protocol method as:
-(void)applicationLoggedIn:(NSDictionary*)response{
}
Hope this helps.
Two patterns to consider: delegate and block. Block is quicker to code, and I usually prefer it to delegate for network ops. To use a block, write the login service this way:
// LoginService.h
- (void)login:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion;
It sounds like you're using NSURLConnection delegate pattern here, so I will assume that. Please realize that NSURLConnection also provides a nice one-shot block method to do the request.
// LoginService.m
#property (copy, nonatomic) void (^completion)(NSDictionary *, NSError *);
- (void)login:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion {
// copy the block when the request begins
self.completion = completion;
// start your request, as you have it now
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSDictionary *dictionary = // parse the data you collected into a dictionary
// invoke the block with the result
self.completion(dictionary, nil);
self.completion = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
self.completion(nil, error);
self.completion = nil;
}
It's polite to dispose of the block (set it to nil) after you invoke it, so it doesn't retain any part of the calling context.
Basically you need ViewController to have a public method which LoginService can call when it's done its job, and NSDictionary will be a parameter to this method. LoginService will need a reference back to ViewController in order to invoke this method, so define a public property on LoginService which will hold a ViewController reference - and set this right after instantiating LoginService.
Of course, if you want LoginService to be more reusable, and not tied to ViewController specifically, delegates are the way to go. LoginService would define the LoginServiceDelegate protocol with the method to be called on completion. ViewController would then implement the LoginServiceDelegate protocol. The public property on LoginService becomes a LoginServiceDelegate reference, and so LoginService no longer needs to import ViewController. This way, ViewController is dependent on LoginService, but LoginService is not dependent on ViewController.