We have a product where the user registers by providing their phone number.
However after they register they could potentially change their sim.
Is it possible to programatically determine if the sim has been removed or inserted?
(Thanks if you provide it, but any digression comments on the use of using the phone number in the first place would be irrelevant to this discussion, I don't want to discuss that aspect of things, only the sim aspect).
Yes, of course it is possible. Link CoreTelephony.framework to make following code compile:
CTTelephonyNetworkInfo* info = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier* carrier = info.subscriberCellularProvider;
NSString *mobileCountryCode = carrier.mobileCountryCode;
NSString *carrierName = carrier.carrierName;
NSString *isoCountryCode = carrier.isoCountryCode;
NSString *mobileNetworkCode = carrier.mobileNetworkCode;
// Try this to track CTCarrier changes
info.subscriberCellularProviderDidUpdateNotifier = ^(CTCarrier* inCTCarrier) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"User did change SIM");
});
};
By values of mobileCountryCode, mobileNetworkCode, carrierName, isoCountryCode you can judge about presence of SIM. (Without SIM they become incorrect).
There is also some undocumented functions/notifications in CoreTelephony, but your app may be banned by Apple if you'll use them. Anyway:
// Evaluates to #"kCTSIMSupportSIMStatusReady" when SIM is present amd ready;
// there are some other values like #"kCTSIMSupportSIMStatusNotInserted"
NSString* CTSIMSupportGetSIMStatus();
// Use #"kCTSIMSupportSIMStatusChangeNotification" to track changes of SIM status:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(SIMNotification:)
name:#"kCTSIMSupportSIMStatusChangeNotification"
object:nil
];
// This one copies current phone number
NSString* CTSettingCopyMyPhoneNumber()
Addendum Another possible (and legal) solution: if your company has a database of phone numbers, you can send an sms or call(and cut) any specific number to verify that user still uses the same phone number.
UPDATE Function NSString* CTSettingCopyMyPhoneNumber() doesn't work anymore (returns empty string).
Related
In Android we can detect whether the phone is Single SIM or Dual SIM. And as we know iPhone XS, iPhone XS Max, iPhone XR, and later feature Dual SIM with a nano-SIM and an eSIM except china mainland where there is no eSIM.
A) I can programatically invoke MFMessageComposeViewController
from a button click to send the SMS to a particular number. Here is
the following code: This code can tell us whether the SIM is
inserted or not. But will it work for eSIM too?
-(void)openMessageViewWithName:(NSString*)contactName withPhone:(NSString *)phone{
CTTelephonyNetworkInfo *networkInfo=[[CTTelephonyNetworkInfo alloc]init];
CTCarrier *carrier=networkInfo.subscriberCellularProvider;
NSString *Countrycode = carrier.isoCountryCode;
if ([Countrycode length]>0) //Check If Sim Inserted
{
[self sendSMS:msg recipientList:[NSMutableArray arrayWithObject:phone]];
}
else
{
[AlertHelper showAlert:#"Message" withMessage:#"No sim card inserted"];
}
//Method for sending message:
- (void)sendSMS:(NSString *)bodyOfMessage recipientList:(NSMutableArray *)recipients{
MFMessageComposeViewController *controller1 = [[MFMessageComposeViewController alloc] init] ;
controller1 = [[MFMessageComposeViewController alloc] init] ;
if([MFMessageComposeViewController canSendText])
{
controller1.body = bodyOfMessage;
controller1.recipients = recipients;
controller1.messageComposeDelegate = self;
[self presentViewController:controller1 animated:YES completion:Nil];
}
}
B) Is it possible to determine whether my iPhone is Dual SIM or Single SIM programatically? If yes it is DUAL SIM then can we choose the SIM from my application to send the SMS ?
Please help me with these and help me with a workaround .
As #PaulW11 mentioned in comment "No, you cannot detect sim details nor select the sim that is used to send messages. This is controlled by the user
"
Read Carefully for understanding
A) First of all no SIM related datas or phone number you are going to get in iOS, the only details you get is regarding the network like carriername,mobilecountrycode,isocountrycode,mobilenetworkcode. So if you replace a Verizon SIM with Verizon you will see the carrier name as Verizon only , nothing else you are going to get
SOLUTION Invoke MFMessageController from a button click as you are doing.This opens the iOS default SMS/Messages app. Then allow the user to choose the SIM in case of Dual SIM from the Messages App itself.
B) It is not possible to select the particular SIM/Phone Number inside your application. Because there is no provision in iOS to do that as in Android. Instead you must invoke the default messages app in iOS where you can choose your desired SIM (in case of DUAL sim) for sending SMS or else in case of single SIM it directly sends the message from that number
did you try this?
CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc]init];
if (#available(iOS 12.0, *)) {
NSDictionary *providers = [networkInfo serviceSubscriberCellularProviders];
NSEnumerator *e = [providers objectEnumerator];
if ([[e.allObjects firstObject] isKindOfClass:(CTCarrier.class)]) {
NSString *str = ((CTCarrier *)[e.allObjects firstObject]).isoCountryCode;
}
} else {
// do a fallback
}
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!
Is there any way to access the last time an ABAddressBook contact was accessed, interacted with, called, miss-called, etc.
I'm aware of the ABPerson properties, specifically kABPersonModificationDateProperty. But I was wondering if there any way of knowing more about the users interaction with that contact.
No apple does not allow access to the Call list. Since a call information is stored in the call and not in the addressbook there is no way to get the information you want from the addressbook.
I don't think you can access called history in iOS, especially after iOS 4. You can however know that a phone call was dialled using CoreTelephony framework.
I do it in applicationDidBecomeActive of my AppDelegate.m
...
typeof(self) __weak weakSelf = self;
self.center = [[CTCallCenter alloc]init];
self.center.callEventHandler = ^(CTCall *call) {
if(call.callState == CTCallStateDialing) {
weakSelf.callWasMade = YES;
}
};
...
I used CoreTelephony framework introduced in iOS SDK 4.0 to know about Incoming call & its dropped state.
CTTelephonyNetworkInfo *tni = [[CTTelephonyNetworkInfo alloc] init];
callCenter = [[CTCallCenter alloc] init];
crtCarrierName = tni.subscriberCellularProvider.carrierName;
[callCenter setCallEventHandler:^(CTCall *call) {
if ([[call callState] isEqual:CTCallStateConnected]) {
//this call has just connected
} else if ([[call callState] isEqual:CTCallStateDisconnected]) {
//this call has just ended (dropped/hung up/etc)
}
}];
Can i use this event handler to track call state when my app is in background?
Can i also fetch incoming call phone number from CTCall object? or there is any other way around.
I dont want to use Private API.Is there way available from Apple iOS SDK?
No there is no way to do this in the official SDK, you can not use it in the background since it does not fall on of the background running categories unless you app does something else in the background then just monitoring the call.
You will never be able to get the phone number of the current call since this is private data Apple will not allow you to acces the data.
In iPhone can we set the lock screen, wallpaper and ringtone programmatically?
If Yes, then please let me know how to set them?
This can all be done easily, but will be rejected by Apple.
The ringtone can be changed by altering com.apple.SpringBoard.plist, specifically the ringtone key.
The following code can be used to read the actual ringtone title of custom ringtones (synced by iTunes).
NSMutableDictionary *custDict = [[NSMutableDictionary alloc] initWithContentsOfFile:#"/private/var/mobile/Media/iTunes_Control/iTunes/Ringtones.plist"];
NSMutableDictionary *dictionary = [custDict objectForKey:#"Ringtones"];
NSArray *keys = [dictionary allKeys];
id key = [keys objectAtIndex:indexPath.row];
NSMutableDictionary *customRingtone = [dictionary objectForKey:key];
NSString *name = [customRingtone objectForKey:#"Name"];
cell.textLabel.text = name;
The Wallpapers can be overwritten at:
NSString *homePath1 = #"/private/var/mobile/Library/SpringBoard/HomeBackground.jpg";
NSString *homePath2 = #"/private/var/mobile/Library/SpringBoard/HomeBackgroundPortrait.jpg";
NSString *lockPath1 = #"/private/var/mobile/Library/SpringBoard/LockBackground.jpg";
NSString *lockPath2 = #"/private/var/mobile/Library/SpringBoard/LockBackgroundPortrait.jpg";
These examples were used in one of my Cydia apps. Theres not really much more to them, but these should get you going in the right direction.
The answer by WrightsCS stopped working at some point due to a change in iOS. Unfortunately, this is something you have to live with if you wish to use undocumented features.
If you still need to do this, for non-App Store apps only, this code works in iOS 9.3. It could stop working in any future iOS release, though. (see comment below: no longer working in iOS 10)
#import "SBSUIWallpaperPreviewViewController.h"
#import <dlfcn.h>
// open the private framework dynamically
void *handle = dlopen("/System/Library/PrivateFrameworks/SpringBoardUIServices.framework/SpringBoardUIServices", RTLD_NOW);
UIImage *wallpaper = [UIImage imageNamed: #"background.jpg"];
Class sbClass = NSClassFromString(#"SBSUIWallpaperPreviewViewController");
// we create a view controller, but don't display it.
// just use it to load image and set wallpaper
SBSUIWallpaperPreviewViewController *controller = (SBSUIWallpaperPreviewViewController*)[[sbClass alloc] initWithImage: wallpaper];
[controller setWallpaperForLocations: 3]; // 3 -> set both for lock screen and home screen
dlclose(handle);
You'll need to add the private API header to your project. You can usually find these online with a little searching, for example, here.
In the example above, [SBSUIWallpaperPreviewViewController setWallpaperForLocations:] is called with an argument of 3: 3 indicates the image should be used for both lock and home screens. 1 indicates Lock screen only. 2 indicates Home screen only.
For an explanation of why I open this framework up dynamically, see my related answer here.
I don't have an answer regarding ringtones. This really should be a separate question: completely different APIs at work.
use private api if you can
check PLStaticWallpaperImageViewController