Core Spotlight objective c - ios

I am trying to add Core Spotlight search to my app. I have imported
#import <MobileCoreServices/MobileCoreServices.h>
#import <CoreSpotlight/CoreSpotlight.h>
then added
[self setupCoreSpotlightSearch];
under viewDidLoad. Then added the following code.
- (void)setupCoreSpotlightSearch
{
if ([CSSearchableIndex isIndexingAvailable]) {
NSLog(#"Spotlight indexing is available on this device");
CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeImage];
// Set properties that describe attributes of the item such as title, description, and image.
NSString *title = _locationTittle;
attributeSet.title = title;
attributeSet.contentDescription = [NSString stringWithFormat:#"%#",title];
attributeSet.keywords = #[ title ];
// Create an attribute set for an item
UIImage *image = _locationImage;
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
// Create a searchable item, specifying its ID, associated domain, and the attribute set you created earlier.
CSSearchableItem *item;
NSString *identifier = [NSString stringWithFormat:#"%#",attributeSet.title];
item = [[CSSearchableItem alloc] initWithUniqueIdentifier:identifier domainIdentifier:#"com.mybundleid.productname.search" attributeSet:attributeSet];
// Index the item.
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
if (!error) {
NSLog(#"error = %#", error);
}];
} else {
NSLog(#"Spotlight indexing is not available on this device");
}
}
Note I have added my bundle id with .search after it.
When I try to search for the name of the landmark, which is _locationTittle nothing shows up. I cannot understand this and have tried to follow other tutorial and example code. Can someone please help.
Update. This seems to be working and just loads the app when pressed as if the app was just normally loaded. I added it into my view controller for looking in more detail at the landmark. This means that the user has to have clicked the landmark maker for it to start showing up in spotlight. Ideally I would like it to show all the landmarks without the user having to open them all for them to show in spotlight, then they can press the landmark for that specific landmarks detail view to be loaded. In my view controller where all the data is handled then passed to my detail view of the landmark is this code, which passes the data into the detail view:
- (void)locationClicked:(ARGeoCoordinate *)coordinate{
NSLog(#"%#", coordinate);
[Answers logCustomEventWithName:coordinate.locationTittle
customAttributes:#{}];
//user has tap on location, we need to show the location details view here
detailViewController *vc = (detailViewController*)[[self storyboard] instantiateViewControllerWithIdentifier:#"detailViewController"];
vc.locationImage = coordinate.imgLocation;
vc.locationTittle = coordinate.locationTittle;
vc.locationTxt = coordinate.locationTxt;
vc.PhotoCreditText = coordinate.photoCreditTittle;
[self.navigationController pushViewController:vc animated:YES];
}
This all comes from:
- (NSMutableArray *)geoLocations {
NSMutableArray *locationArray = [[NSMutableArray alloc] init];
ARGeoCoordinate *tempCoordinate;
CLLocation *tempLocation;
UIImage *locationImage;
// test locations
tempLocation = [[CLLocation alloc] initWithLatitude:53.712371 longitude:-1.882742];
tempCoordinate = [ARGeoCoordinate coordinateWithLocation:tempLocation locationTitle:#"Wainhouse Tower"];
locationImage = [UIImage imageNamed:#"WainhouseTower.jpg"];
[tempCoordinate setImgLocation:locationImage];
[tempCoordinate setLocationTxt:#"Wainhouse Tower is a folly in the parish of King Cross. At 275 feet (84 m), it is the tallest structure in Calderdale and the tallest folly in the world, and was erected in the four years between 1871 and 1875. The tower was completed on 9 September 1875, at a cost of £14,000. The main shaft is octagonal in shape and it has a square base and 403 steps leading to the first of two viewing platforms.\n\nOne driving force behind the erection of the viewing platforms was a long standing feud between Wainhouse and his neighbour, landowner Sir Henry Edwards. Edwards had boasted that he had the most private estate in Halifax, into which no one could see. As the estate was on land adjacent to the chimney's site, following the opening of the viewing platforms, Edwards could never claim privacy again.\n\nThe tower was designed by architect Isaac Booth as a chimney to serve the dye works owned by John Edward Wainhouse (1817–1883). The height of the chimney was to satisfy the Smoke Abatement Act of 1870 which required a tall chimney to carry smoke out of the valleys in which the factories were built. A much simpler chimney would have satisfied the requirements but Wainhouse insisted that it should be an object of beauty.\n\nIn 1874 John Wainhouse sold the mill to his works manager who refused to pay the cost of the chimney's construction so Wainhouse kept the tower for himself and used it as an observatory. Booth left after a dispute and was replaced by another local architect, Richard Swarbrick Dugale, who is responsible for the elaborate galleries and the corona dome at the top.\n\nThe tower is open to the public during bank holidays, and is a Grade II listed building.\n\n\nMaterial has been used from the Wikipedia article http://en.wikipedia.org/wiki/Wainhouse_Tower see that article's history for attribution.\n\nWainhouse Tower by James Preston https://flic.kr/p/cCMB2A is licensed under CC BY 2.0"];
[tempCoordinate setPhotoCreditTittle:#"See attribution below"];
[tempCoordinate setLocationTittle:#"Wainhouse Tower"];
[locationArray addObject:tempCoordinate];
return locationArray;
}
How do I make it so core spotlight works so the detail view doesnt have to have been loaded for each landmark by using the above code, and when the spotlight search is pressed it calls the above code?

Related

Loading a list into a NSMutableArray to populate cells of a UITableViewController

I am building an app which contains a UITableViewController with a list of languages. When I click on English for example, it will load up another UITableViewController with a list of Videos available on a website for that language. The cells here will differ from clicking on French. So I have a Title and a URL for each video. I'm not keeping the video within the app; the user simply will click on one of the Titles and it will go to the website to play that video.
There are about 1,100 videos, so putting this is code is quite painful. Essentially, from the prepareForSegue, I am calling a UITableViewController and will display a number of cells depending on how many there are for that language.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.languagesTabTableView indexPathForCell:sender];
[self.languagesTabTableView deselectRowAtIndexPath:indexPath animated:YES];
if ([segue.identifier isEqualToString:#"ShowingLeafletsAndVideosSegueChinese"])
{
LeafletsAndVideosTableViewController *lvtvc = (LeafletsAndVideosTableViewController*)segue.destinationViewController;
NSMutableArray *availableLeafletsToPass = [[NSMutableArray alloc] initWithObjects:#"3 Facts",#"10 Questions",nil];
NSMutableArray *availableVideosToPass = [[NSMutableArray alloc] initWithObjects:#"1",#"2", nil];
NSString *theSelectedLanguage = #"Chinese";
// Calling the set methods on the LeafletsAndVideosTableViewController
[lvtvc setAvailableLeaflets:availableLeafletsToPass];
[lvtvc setAvailableVideos:availableVideosToPass];
[lvtvc setSelectedLanguage:theSelectedLanguage];
}
else if ([segue.identifier isEqualToString:#"ShowingLeafletsAndVideosSegueEnglish"])
{
LeafletsAndVideosTableViewController *lvtvc = (LeafletsAndVideosTableViewController*)segue.destinationViewController;
NSMutableArray *availableLeafletsToPass = [[NSMutableArray alloc] initWithObjects:#"3 Facts",nil];
NSMutableArray *availableVideosToPass = [[NSMutableArray alloc] initWithObjects:#"Video 1",#"Video 2", #"Video 3", #"Video 4", #"Video 5", #"Video 6", nil];
NSString *theSelectedLanguage = #"English";
// Calling the set methods on the LeafletsAndVideosTableViewController
[lvtvc setAvailableLeaflets:availableLeafletsToPass];
[lvtvc setAvailableVideos:availableVideosToPass];
[lvtvc setSelectedLanguage:theSelectedLanguage];
}
So availableVideosToPass is filled up with "Titles" and as long as it's not nil, it will get shown in the LeafletsAndVideosTableViewController without issues. What you're seeing above is just a small subset; the videos have much longer titles and actually spread across 1,100 different videos. That's 1,100 different titles.
I'm quite new to this concept and I don't want to change this code around too much, but is there any way I can load in a file, like a text file, or a spreadsheet of titles? The English selection is always going to have the 1,100 titles, so if I could essentially load in a file into the NSMutableArray availableVideosToPass.
If this is possible, how would I go about achieving this? I've never done anything like this before so I'm not even sure what to look for online.
Any guidance on this would be really appreciated.
Create a simple array and send [writeToFile:] message to it
(https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/index.html#//apple_ref/occ/instm/NSArray/writeToFile:atomically:).
Search for this file by its name/contents. Fill all information by editing this file (it may be in "~/Library/Developer/CoreSimulator/Devices/{DEVICE-ID}/data/Containers/Bundle/Application/{APPLICATION-ID}/" subfolder) in your favorite editor or even Xcode.
Load contents of this file in your application by calling class method of NSArray arrayWithContentsOfFile:.
Create a mutable copy of array by invoking method mutableCopy.
More information on file handling is in this answer

Make App Activities and States Searchable by using NSUserActivity

Following is the code that I am trying to implement to make app activities and states searchable but not able to show on iOS search
NSUserActivity *userActivity = [[NSUserActivity alloc]initWithActivityType:#"com.mycompany.activity-type"];
userActivity.title = #"Hello world from in app search";
userActivity.keywords = [NSSet setWithArray:#[#"Hello",#"Welcome", #"search"]];
userActivity.userInfo = #{#"id":#"com.example.state"};
userActivity.eligibleForSearch = YES;
[userActivity becomeCurrent];
Link to make my question more clear.
From the Apple Forums:
One thing that has bitten a few people (myself included) is that the
activity must not be deallocated. If your code is only working with
NSUserActivities (i.e. not using CoreSpotlight in addition) then make
sure your activities aren't being deallocated immediately.
In my
case, I had code that was allocating the NSUA, setting some properties
on it, calling becomeCurrent, but then the object would go out of
scope and deallocated. If you're doing this, try tossing the activity
into a strong property to see if you can then see the results when you
search.
https://forums.developer.apple.com/message/13640#13640
What I have found is you have to assign the NSUserActivity instance you have created to your currently visible UIViewControllers's userActivity property before calling -becomeCurrent. It has fixed it for me and the items immediately appeared both for handoff on other devices and in spotlight search on the same device.
I was experiencing the same issue, and I read on the dev forums that in seed 1 it only works on the device. I was able to make it work on the device.
It might be that this, as with handoff, will only work on the device sadly.
I couldn't get it to work with beta 2 either.
Using a CSSearchableItemAttributeSet with
CSSearchableItemAttributeSet* attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString*)kUTTypeImage];
attributeSet.title = myobject.title;
attributeSet.keywords = [myobject.desc componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
attributeSet.contentDescription = myobject.desc;
if (myobject.images.count > 0) {
attributeSet.thumbnailData = myobject.myimagedata;
}
attributeSet.rating = #(myobject.rating.integerValue / 2);
CSSearchableItem* item;
item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"..." domainIdentifier:#"..." attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
NSLog(#"Search item indexed");
}];
works, though, even with images.
What I couldn't get to work was the rating to show up anywhere.

iOS - Wireless Communication Lagging

So I'm making a game that involves wireless communication between multiple iPhones, with one being the host. I am attempting to do so via the MultipeerConnectivity framework, and I've made a MCManager class (an instance of which I put into appDelegate so it's available throughout the app) to handle sending data from one system to another. This is how sending is implemented in my code:
- (void) sendState: (NSString*) str;
//used by the host to send commands to the other connected systems
{
if(appDelegate.mcManager.connected && iAmHost){
NSData *dataToSend = [str dataUsingEncoding: NSUTF8StringEncoding];
NSArray *allPeers = appDelegate.mcManager.session.connectedPeers;
NSError *error;
[appDelegate.mcManager.session sendData:dataToSend
toPeers:allPeers
withMode:MCSessionSendDataReliable
error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
}
}
}
and when the subordinate systems receive the data, MCManager sends the Notification Center a notification and my class, which is looking for that particular notification, grabs it and executes this:
-(void)didReceiveDataWithNotification:(NSNotification *)notification{
if(!iAmHost){
NSData *receivedData = [[notification userInfo] objectForKey:#"data"];
NSString *action = [[NSString alloc] initWithData: receivedData encoding:NSUTF8StringEncoding];
NSLog(#"Recieved:");
NSLog(action); //for debugging purposes, and figuring out timing
//decide how to act depending on the string given
if([action containsString:#"ChangeMaxScore"]){
//the string was formatted as, for example, "ChangeMaxScore105"
NSString* valueStr = [action substringFromIndex:14];
maxScore = (int)[valueStr integerValue];
[self changeMaxScore]; //this method changes the label text that shows the user the value of maxScore
}
else if([action containsString:#"ChangePlayerNo"]){
//strings are formatted as "ChangePlayerNo2" for the second segment in a segmented control with the segments "2", "3", "4"
//so it would be referring to four players
NSString *valueStr = [action substringFromIndex:14];
[playerNumberSegmentedControl setSelectedSegmentIndex: [valueStr integerValue]];
playerNumber = playerNumberSegmentedControl.selectedSegmentIndex + 2;
[self changePlayerNumber];
//Players can either be human or a type of AI (AI-1,AI-2,etc.)
//the segmented control where you choose this is invisible unless that player number is playing
//so this method sets that segmented control visible and interactable (by adding it to the view)
//and removes those segmented controls not in use from the view
}
else if([action containsString:#"ChangeP0State"]){
//changes the value of the first player's segmented control (Human, AI-1, AI-2, etc.
NSString* valueStr = [action substringFromIndex:13];
AIControl0.selectedSegmentIndex = (int)[valueStr integerValue];
}
...
else if([action containsString:#"StartGame"])
[self newGame];
//this method starts the game and, in the process, pushes another view controller
}
}
My issue is that these actions, on the receiving end, are very laggy. For changing the number of players, for instance, the receiver NSLogs "Received: ChangePlayerNo1", and the segmented control on-screen changes its selected segment to the second one, but the stuff that's supposed to show up at that point...doesn't. And when I send the "StartGame" command, the receiver NSLogs that it has received it, I have to wait thirty seconds for it to actually start the game like it was asked.
This delay makes it very hard to test whether my wireless methods are working or not (it works on the host's side, mostly because the host is changing them manually, not responsively - also, all of this works on the other side of my program, which is just the game without wireless support, with several players/AIs on a single screen) and, if not fixed, will definitely prevent the app from being used easily.
I'm curious what causes this and what I can do to fix it. Thank you!

Creating multiple themes in an iPhone App

I have recently released my first app to the App Store and still have a very long way to go with iOS development.
I'm looking to introduce themes into my app as an update so the user can select from 4-5 different themes.
I've got a tab bar controller and have set the 5th tab to be the "Settings" tab which contains a Table View with cells. The first cell contains the text "Themes" where the user can select it, be taken to a new Table view/Collection View to select the themes.
So I searched online and came across this incredible answer on doing just this:
How to create Multiple Themes/Skins for iphone apps?
Because I'm still new to development, I'm in need of assistance to take this forward.
To start off with, I have two themes:
1) DefaultTheme (Newiphonebackground.png)
2) PurplePinkTheme (Purplepinknew.png)
Following the instructions, I have created one plist for the Default theme and one plist for the PurplePink theme.
In the ThemeManager class that I created, I have:
- (id)init
{
if ((self = [super init]))
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *themeName = [defaults objectForKey:#"theme"] ?: #"DefaultTheme";
NSString *path = [[NSBundle mainBundle] pathForResource:themeName ofType:#"plist"];
self.styles = [NSDictionary dictionaryWithContentsOfFile:path];
}
return self;
}
+ (ThemeManager *)sharedManager
{
static ThemeManager *sharedManager = nil;
if (sharedManager == nil)
{
sharedManager = [[ThemeManager alloc] init];
}
return sharedManager;
}
In my table view, where the theme will get applied (it's going to get applied to every screen in the app but this is just to start off with where I'm testing with one scene in the app), in the viewDidLoad, I put:
NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *imageName = [styles objectForKey:#"DefaultTheme"];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
self.tableView.backgroundView = backgroundImageView;
That does nothing different because it applies the default theme. However if in the ThemeManager, I change the plist to be:
NSString *themeName = [defaults objectForKey:#"theme"] ?: #"PurplePinkTheme";
and in the viewDidLoad of the separate Table View, I set the code to be:
NSString *imageName = [styles objectForKey:#"PurplePinkTheme"];
Then upon loading my application, my PurplePinkTheme loads.
So far so good, but I don't think I've done anything substantial here.
I am confused from this point on. I am looking to start off with changing just the background images from the in-app settings and once I have that done, I'll look to change the custom navigation bars, etc.
So my questions are:
1) Why do I create one plist per theme?
2) How exactly do I link multiple plists in the ThemeManager class?
3) How do I link all of the themes up to the settings Theme cells allowing the user to choose?
I'm sorry this is vague, but I really need to understand how exactly I can carry on here. For example, I just don't get how to have multiple plists in the ThemeManager and how to move forward from here.
To recap, I, for now just want the ability to have multiple plist files with the different backgrounds, and for the user to go the settings tab in my app, click on the "Themes" cell and be able to select a different theme for the app without restarting the app.
I know the tutorial does carry on with explanations on that, but I'm just not quite sure I understand it all.
Any guidance on this would be massively appreciated.
Many thanks,
Your question is super long so I must confess I did not read the whole thing. That said I spent a lot of time with themes and the best solution I have found is to create an object that handles formatting. This is how I think you can implement it:
Create a formatter object extending NSObject
Have a property for each changeable piece of the theme:
for example if the background changes images you can have a UIImage in there called background. If the font color changes you have a UIFont property in there.
create a shared instance of your formater by adding:
+(Formater *) sharedInstance; //add to .h
+ (Formater *) sharedInstance //add to .m
{
if (!_sharedInstance)
{
_sharedInstance = [[Formater alloc] init];
}
}
Now in your view controller all you need to do is create a reference to your shared item and use that to style your view controller. (remember to make the changes in viewWillAppear not or it will not change after the setting is changed.
Voila!

Using Core Data with Images or buttons (or anything besides a tableView)

I'm trying to build an application that uses something like the following data model. I can do this fine in a tableView but I would like to be more creative. For Example, Inside a navigation controller, I want to have a view that has 10 images (or buttons with an image as a background).
(three entities)
entity 1: House
attribute: houseName
relationship:person
entity 2:Person
attribute: personName
relationship:house
relationship:children
entity 3:Children
attribute:name
attribute:birthPlace
relationship: adult
relationships are one to many down from
attributes are all strings
person<---->>house, children<--->>adult
In the first view there are images and each image is assigned to entity House (houseName 1, houseName 2, etc.). When you select a house it pushes the next view with 5 images (that is linked to the Person Entity (personName 1, personName2, etc). When you select the PersonName it will push the next view populated with the Children Entity.
I've read quite a bit of info on core data and I'm comfortable doing this:
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [[managedObject valueForKey#"houseName"] description];
but very unsure where I should even start with using and image or button to do this
I was thinking of something like this:
-(id)initWithHouseViewController:(HouseViewController*)aHouseViewController house:(NSManagedObject *)aHouse{
if blah blah
self.houseViewController = aHouseViewController;
self.house = aHouse
}return self;
}
// for a selector
-(void) showPersonView{
PersonViewController *pvc = [[PersonViewController alloc] initWithHouseViewController:self house:house];
in the viewDidLoad
{
if (house != nil) {
UIImageView *house1 = [[ UIImageView alloc.. blah blah
some kind of.. action:#selector(showPersonView)
((for each houseName in house) instead of house1, house2,)
...
}
Any suggestions with the viewDidLoad where I wouldn't need to hard code in each image (or button) would be great to make this more portable. Also, I'm not rooted to doing it this way if there are suggestions as to a nicer/more creative way to do this.
Sorry if this is a bit messy. It's messy in my mind as well.
Thanks for taking your time to read this,
I wouldn't use FetchResultController in this case.
Instead You can try This:
Create a view controller that will show all the houses as buttons, you can dynamically create the buttons according to the "Houses" count.
It will look something like this:
NSArray * allHouses = [Houses allObjects];
//you can sort them if you need
[allHouses enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
//create the buttons as custom and you can add any image to the button
UIButton *button....
button
//store the house index as the button tag, so you can get it later
button.tag = idx;
//add a selector to the button
[button addTarget:self
action:#selector(getPersons:)
forControlEvents:UIControlEventTouchUpInside];
}];
Create a new PersonViewController that will hold the persons. the view controller should get the house as the parameter and not the person. After it gets the house you will get the persons with the relationShip.
Now add this method to get the :
-(void)getPersons(UIButton*)sender{
NSInteger *tag = sender.tag;
House * house = (House*)[allHouses objectAtIndex:tag];
PersonViewController *pvc = [[PersonViewController alloc] init];
pvc.house = house;
[self.navigationController pushViewController.....];
}
Inside the person view controller you can get all the persons or with fetch request or simply with :
NSArray *allPersons = [house.person all objects];
Again create the persons buttons the same as you did with the first view controller.
Reapit the same procedure with the Children view controller.
GoodLuck
Shani

Resources