ibeacon ranging in oin background output to second view controller - background-process

I'm working on a demo app with iBeacons. At this moment I have the ranging for the beacons working in my firstviewcontroller, when I want to show a detailed view of the beacon data in a second view controller I do hear the the audible feedback on beacon change which means the loop of detecting closest beacons is still running in view controller 1 ... but how can I get updates from view controller 1 in my second view controller?
I tried passing them in the segue, but then it is static NSString data, can anyone help me on how to get the "live" data in the second view controller?

If you want to process ranged iBeacon data in more than one ViewController, you can set up ranging in your AppDelegate, then call public methods on each ViewController from there. The main didRangeBeacons: inRegion: in your AppDelegate can call the custom methods on each ViewController if that ViewController has been activated.
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
if (self.firstViewController != Nil) {
[self.firstViewController handleBeacons: beacons];
}
if (self.secondViewController != Nil) {
[self.secondViewController handleBeacons: beacons];
}
}
In order to do this as above, you must maintain properties for each ViewController on your AppDelegate:
#interface MyAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) FirstViewController *firstViewController;
#property (strong, nonatomic) SecondViewController *secondViewController;
#end
You can populate them from each ViewController like this:
- (void)viewDidLoad {
[super viewDidLoad];
MyAppDelegate *appDelegate = (MyAppDelegate) [[UIApplication sharedApplication] delegate];
appDelegate.firstViewController = self;
}

Related

Passing data through a UINavigationController to a UIViewController

When an item is selected by the user, I'm trying to pass the selected item's index along with a reference to the records controller to a UINavigationController. The problem I'm having is that I'm not sure how to pass those variables properly. I was trying to avoid passing them 'the wrong way'. [EDIT: The application works perfectly if I save the items in static variables within AppDelegate]
Below I show the code that runs when the user selects an item, the code that runs in the navigation controller (which uses an incorrect way to pass the variables), and finally the code that runs on the view controller that indicates the variables are not being passed.
Here's the code for when the user selects an item:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let storyboard = storyboard, let controller = storyboard.instantiateViewController(withIdentifier: "DetailNavigationController") as? DetailNavigationController else {
print("ERROR: didn't get the view controller we expected")
return
}
print("initializing the newly created page view controller with index \(indexPath.row)")
controller.bIndexPath = indexPath
controller.bController = bController
present(controller, animated: true, completion: nil)
}
.
That causes the `DetailNavigationController.viewDidLoad()` to run:
override func viewDidLoad() {
super.viewDidLoad()
print("DetailNavigationController vdl and index \(bIndexPath.row)")
guard let storyboard = storyboard, let controller = storyboard.instantiateViewController(withIdentifier: "ManagePageViewController") as? ManagePageViewController else {
print("ERROR: didn't get the view controller we expected")
return
}
controller.bIndexPath = bIndexPath
controller.bController = bController
}
.
The output from that proves that the data is being passed this far:
DetailNavigationController vdl and index 4
But in ManagePageViewController we apparently don't get the same instance, because the variables are not set. I "knew this wouldn't work", but didn't know what else to try.
So below we see the ManagePageViewController.viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad ManagePageViewController \(bIndexPath)")
if let viewController = viewPhotoCommentController(currentIndex ?? 0) {
if let bController = bController {
viewController.bController = bController
} else {
print("ManagePageViewController viewDidLoad bController.performFetch")
let bSortedByName = SaucerItem.order(Column("name").asc, Column("name"))
bController = try! FetchedRecordsController(dbQueue, request: bSortedByName)
try! bController.performFetch()
vController.brewController = bController
}
let viewControllers = [viewController]
print("setting a view controller instance with currentIndex \(currentIndex ?? 0)" )
setViewControllers(viewControllers, direction: .forward, animated: false, completion: nil)
}
dataSource = self
}
.
And the output that shows going down the path of making new `bController`:
viewDidLoad ManagePageViewController nil
ManagePageViewController initializing the newly created page view controller with index 0
ManagePageViewController viewDidLoad bController.performFetch
setting a view controller instance with currentIndex 0
.
So that indicates the variables set earlier must have been on some other instance, so that's the wrong approach.
I thought I could make the settings in AppDelegate, but that just seemed wrong to me. There must be a way to pass the data cleanly. Some other things I tried included using the facilities contained within the UINavigationController to figure out what it was going to run, and set the variables on that instance, but I wasn't able to discover the instance that the controller was going to run.
Additional Information:
In order to answer a question about why it seems like I'm going from "A" to "C" through "B", I'll place a diagram here and try to explain why. The bottom line is that it must be configured this way to enable the paging on the detail screen.
Printing controller from DetailNavigationController.viewDidLoad() said ManagePageViewController, and printing viewController from ManagePageViewController.viewDidLoad() said PageViewController.
So what's the right way to pass this information to the next view controller?

How to delete a row in the 1st Interface Controller by pressing a button in the 2nd Interface Controller ( Modal View )

I have a table in the 1st interface controller , when a press on a row , a modal interface controller opens up , it contains a button.
I want the button to delete the row in the first interface controller.
Here is my code :
In the first interface controller
Blockquote
// It opens up a modal view ( with the context of the tapped row )
override func contextForSegueWithIdentifier(segueIdentifier: String, inTable table: WKInterfaceTable, rowIndex: Int) -> AnyObject? {
var timelineRow = timeline.reverse()
return timelineRow[rowIndex]
}
Blockquote
And here is my code in the second interface controller
Blockquote
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
sentContext = (context as? Dictionary)!
sentRow = sentContext
//sentRow contains the context
}
#IBAction func deleteRow() {
var sentRow : [String:String] = ["action":"delete"]
NSNotificationCenter.defaultCenter().postNotificationName("notification_DeleteRow", object: nil, userInfo: sentRow)
dismissController()
}
Blockquote
I've sent the index of the row through the contextForSegueWithIdentifier.
In the 2nd Interface Controller I've extracted the Context and put it in variable
I then send back the userInfo throught the NSNotificationCenter
My Problem :
How can I use the userInfo sent back from the modal controller in order to delete the tapped row.
How would I manage to delete the tapped row (1st IC) by pressing on the delete button situated in the (2nd IC)
There are a few options in this situation:
You could use NSUserDefaults, and while it would work, this isn't how that class is intended to be used.
You can create your own custom NSNotification and broadcast it from the modal controller. Your first interface controller would listen for this event and delete the appropriate record.
You can pass a reference to your first interface controller to the modal controller and retrieve it in awakeWithContext:. This allows you to set the first interface controller as a delegate. Once this happens, you can define whatever protocol you'd like to inform the first controller of important events.
I have a blog post that goes into more detail on the second two topics: Advanced WatchKit Interface Controller Techniques
This can be achieved with custom delegate easily,
#protocol MyCustomDelegate <NSObject>
- (void)deleteButtonTapped:(id)sender;
#end
- (IBAction)deleteButtonTapped:(id)sender {
if ([self.delegate respondsToSelector:#selector(deleteButtonTapped:)]) {
[self.delegate deleteButtonTapped:sender];
};
}
More detailed answer is here.

WKInterfaceTable's didSelectRowAtIndex never gets called in WKInterfaceController

I have a WKInterfaceController and I added a table as following:
// .h
#interface InterfaceController : WKInterfaceController
#property (weak, nonatomic) IBOutlet WKInterfaceTable *table;
#end
// .m
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"did select");
}
- (void)table:(WKInterfaceTable *)table
didSelectRowAtIndex:(NSInteger)rowIndex{
NSLog(#"did select new");
}
However neither of the two methods gets called. I was unable to find any protocol declaration for WKInterfaceTable and neither any delegate property on the table.
Is there something that I am missing here?
I found out that the method was never called because I had set a segue to be triggered on selection of the row on Interface builder.
It seems that that by having no delegation and table protocols once you set a delegate it stops the didSelectRow method from being called.
In Apple's WKInterfaceController document it states that if you do not have any actions or segues then the method called is:
- table:didSelectRowAtIndex:
If you use segues then the methods called are:
For buttons:
- contextForSegueWithIdentifier:
For tables:
- contextForSegueWithIdentifier:inTable:rowIndex:
Swift 4
Here’s an example of selecting a WKInterfacetable row in a REST/JSON implementation.
Create a context property instance of the array class instead of using self.pushController.
override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
let message = messageObjects[rowIndex]
presentController(withName: "MessageView", context: message)
}

iOS7 / XCode5 - "viewDidLoad" called at once, rather than when "presentViewController" was called

I have just upgraded to XCode5 and iOS7 and now my application has stopped working.
I am creating a new view based on a property of a current view, and I need to set some properties of the new view before I display it.
Previously, I did it like this :-
hqView *v = [[hqView alloc] initWithNibName:NULL bundle:NULL];
[v setProperty1:true];
[v setProperty2:false];
[self presentViewController:v animated:TRUE completion:NULL];
This then triggered the [viewDidLoad] method on the view controller, which had the following code in it :-
if ([self property1])
{
[list1 load]
}
else
{
[list2 load]
}
However now the [viewDidLoad] method is triggering as soon as I create the view, meaning that I am not able to set the properties before [viewDidLoad] is called and so it ALWAYS loads list2 regardless of what I actually want.
The thing is - this did NOT happen under iOS6, so I am wondering whether it is a new setting in XCode5 that has caused this to change, or if I am going to have to rewrite it to do what I need it to do?
You cannot know when viewDidLoad, viewWillAppear, etc... will be called.
My advice : Make a dedicated init method to your controller, something like :
#implementation hqView
- (instancetype)initWithProperty1:(BOOL)prop1 property2:(BOOL)prop2
{
// uses default NIB
self = [super initWithNibName:nil bundle:nil];
if (self){
[self setProperty1:prop1];
[self setProperty2:prop2];
}
return self;
}
#end
Set a breakpoint on your viewDidLoad method that is being called before your init method and you will be able to see what is causing the viewDidLoad to be called. you will probably find that it is being called because the view was referenced by some other code. this most often happens in a super class (like if you have a UIViewController superclass that implements common functionality for your view controllers). for example, if you accidentally put new code in that accessed self.view in a method in your superclass such as - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
you would notice this behavior. so make sure you are not accessing the view in any code before you wanted to. -rrh

Subclassing NSString

Using WSDL2ObjC I am getting lot of classes which are subclasses of NSString.
I am finding it troublesome to initialize the NSString value of any object of those classes.
Say my class looks like this :
#interface mClass ; NSString {
int value;
}
Now in my code I would like to use objects of mClass as both NSString and also want to use its attribute value which is an integer.
How can I do that?
I am trying to use code like this
mClass *obj = [[mClass alloc] initWithString:#"Hello"];
But it's showing me an error saying I am using an abstract object of a class , I should use concrete instance instead.
If you really need make NSString subclass you should override 3 methods:
- (instancetype)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer;
- (NSUInteger)length;
- (unichar)characterAtIndex:(NSUInteger)index;
For example:
MyString.h
#interface MyString : NSString
#property (nonatomic, strong) id myProperty;
#end
MyString.m
#interface MyString ()
#property (nonatomic, strong) NSString *stringHolder;
#end
#implemenation MyString
- (instancetype)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer {
self = [super init];
if (self) {
self.stringHolder = [[NSString alloc] initWithCharactersNoCopy:characters length:length freeWhenDone:freeBuffer];
}
return self;
}
- (NSUInteger)length {
return self.stringHolder.length;
}
- (unichar)characterAtIndex:(NSUInteger)index {
return [self.stringHolder characterAtIndex:index];
}
#end
It might be smarter to use a wrapper class that internally uses NSStrings to do whatever operations or manipulations you are trying to do. However this will cause you to need to overload any functionality of NSString you want (such as getting the length of the string).
Or, you could create a category of NSString (found right next to Objective-C class in the new file window). This allows you to add any properties or methods that you wish to be "added" to the NSString class. Now just import this category wherever you wish to use it and you will have all of your custom functions available on any NSStrings objects.
Do you really need to subclass NSString? It’s a class cluster, which (apart from other things) means it’s hard to subclass. There’s a good post by Mike Ash on subclassing class clusters. If you didn’t know that class clusters existed you are probably new to Cocoa and in that case the best short answer is don’t try to subclass class clusters.
There’s also previous questions about subclassing NSString here on Stack Overflow. Next time you might want to search a bit before asking a new question.

Resources