WKWebKit Mac OS NSPrintOperation - objective-c

WebKit 1 exposed the WebFrameView where I could call a print operation.
- (void)webView:(WebView *)sender printFrameView:(WebFrameView *)frameView {
NSPrintOperation *printOperation = [frameView printOperationWithPrintInfo:[NSPrintInfo sharedPrintInfo]];
[printOperation runOperation];
}
With WKWebKit API I can't seem to figure out how to perform a similar action or which view to grab for printing. All my efforts have come up with blank pages.

macOS 11 update: the printOperation(with:) method is no longer private and is now the solution that I use in my apps.
Amazingly, WKWebView still doesn't support printing on macOS, despite the legacy WebView being deprecated.
Looking at https://github.com/WebKit/webkit/commit/0dfc67a174b79a8a401cf6f60c02150ba27334e5 , printing was implemented years ago as a private API, but for some reason, has not been exposed. If you don't mind using a private API, you can print a WKWebView with:
public extension WKWebView {
// standard printing doesn't work for WKWebView; see http://www.openradar.me/23649229 and https://bugs.webkit.org/show_bug.cgi?id=151276
#available(OSX, deprecated: 10.16, message: "WKWebView printing will hopefully get fixed someday – maybe in 10.16?")
private static let webViewPrintSelector = Selector(("_printOperationWithPrintInfo:")) // https://github.com/WebKit/webkit/commit/0dfc67a174b79a8a401cf6f60c02150ba27334e5
func webViewPrintOperation(withSettings printSettings: [NSPrintInfo.AttributeKey : Any]) -> NSPrintOperation? {
guard self.responds(to: Self.webViewPrintSelector) else {
return nil
}
guard let po: NSPrintOperation = self.perform(Self.webViewPrintSelector, with: NSPrintInfo(dictionary: printSettings))?.takeUnretainedValue() as? NSPrintOperation else {
return nil
}
// without initializing the print view's frame we get the breakpoint at AppKitBreakInDebugger:
// ERROR: The NSPrintOperation view's frame was not initialized properly before knowsPageRange: returned. This will fail in a future release! (WKPrintingView)
po.view?.frame = self.bounds
return po
}
}
You can have this be the default print operation for your NSDocument subclass by adding:
override func printOperation(withSettings printSettings: [NSPrintInfo.AttributeKey : Any]) throws -> NSPrintOperation {
return myWebView.webViewPrintOperation(withSettings: printSettings) ?? try super.printOperation(withSettings: printSettings)
}

Here is marcprux swift solution in Objective C:
SEL printSelector = NSSelectorFromString(#"_printOperationWithPrintInfo:");
if ([self.webView respondsToSelector:printSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
NSPrintOperation *printOperation = (NSPrintOperation*) [self.webView performSelector:printSelector withObject:[NSPrintInfo sharedPrintInfo]];
#pragma clang diagnostic pop
return printOperation;
}

Starting on macOS 11 this is not private anymore: printOperation(with:)
Usage:
let info = NSPrintInfo.shared
// configure info...
let operation = webView.printOperation(with: info)

Related

Unsupported action method signature. Must return MPRemoteCommandHandlerStatus

I'm getting this error whenever I'm running simulator on iOS 13+. Everything works for iOS 12 and below so I'm not sure what to do here. Is there anything I can change/edit on my end to make react-native-music-control work for iOS 13?
Exception 'Unsupported action method signature. Must return MPRemoteCommandHandlerStatus or take a completion handler as the second argument.' was thrown while invoking enableControl on target MusicControlManager with params ( pause, 1, { } )
This react-native-music-control probably hasn't updated its iOS MediaPlayer methods.
One common method is MediaPlayer's addTarget. Starting iOS 13, this method must return a MPRemoteCommandHandlerStatus. It used to return nothing in previous iOS versions.
For example, if you have a play method that gets called when the Play button is tapped:
- (void) play {
/* start playing */
}
You are probably registering this play to be called when the Media Player's play command is trigged:
[[MPRemoteCommandCenter sharedCommandCenter].playCommand addTarget:self action:#selector(play)];
Then, all you need is to simply change your play method to return a MPRemoteCommandHandlerStatus like this:
- (MPRemoteCommandHandlerStatus) play
{
// if successfully played
return MPRemoteCommandHandlerStatusSuccess;
// else if there's an issue
// return MPRemoteCommandHandlerStatusCommandFailed;
}
This is in Objective-C. Changing the return value in Swift will be simple too.
Reference: https://developer.apple.com/documentation/mediaplayer/mpremotecommand/1622895-addtarget?language=objc
So this plugin is not being maintained anymore I don't know why, so for those using this plugin the solution is really Simple, based on what #CSawy. I was able to fix it.
1 - Change the method signature to MPRemoteCommandHandlerStatus
All the methods that interact with the control are defined as void so change them to MPRemoteCommandHandlerStatus. They are defined in the file MusicControls and I guess you would find it in your plugins folder. For me it was in myProject/plugins/MusiControls.h
These are the methods that require the change
playEvent
pauseEvent
nextTrackEvent
prevTrackEvent
skipForwardEvent
skipBackwardEvent
After changing all the methods signature u should have
(MPRemoteCommandHandlerStatus) playEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) pauseEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) nextTrackEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) prevTrackEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) skipForwardEvent: (MPSkipIntervalCommandEvent *) event;
(MPRemoteCommandHandlerStatus) skipBackwardEvent: (MPSkipIntervalCommandEvent *) event;
2-Add return to the implementations
Now you should change the implementation of these methods otherwise your app would still be crushing.
Simply open the file MusicControls.m. If you are using Xcode it will highlight the methods for you ... If it's no highlighted then just search for them
For all these functions you will need to add a return ;
For your play event do:
-(MPRemoteCommandHandlerStatus) playEvent:(MPRemoteCommandEvent *)event {
NSString * action = #"music-controls-play";
NSString * jsonAction = [NSString stringWithFormat:#"{\"message\":\"%#\"}", action];
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:jsonAction];
[self.commandDelegate sendPluginResult:pluginResult callbackId:[self latestEventCallbackId]];
return MPRemoteCommandHandlerStatusSuccess;
}
Do this for all the methods and I hope it works.

app broke after Xcode update

I have a Swift app, with some Objective-C code mixed in. It was working yesterday, but this morning I updated XCode and now everything had gone to hell!
Firstly, after updating, I clicked the XCode popup box to allow it to upgrade my app to Swift4. This is where the problems started.
I have a Swift class called RestClient with the following 4 functions (among others):
class func getInstance() -> RestClient {
if (gRestClient == nil) {
let prefs:UserDefaults = UserDefaults.standard
return RestClient(username: prefs.string(forKey: "username")!, password: prefs.string(forKey: "password")!)
}
return gRestClient!
}
class func getUsername() -> String {
if (gUsername == nil) {
let prefs:UserDefaults = UserDefaults.standard
gUsername = prefs.string(forKey: "username")!
}
return gUsername!
}
class func getPassword() -> String {
if (gPassword == nil) {
let prefs:UserDefaults = UserDefaults.standard
gPassword = prefs.string(forKey: "password")!
}
return gPassword!
}
public func getServer() -> String {
return MAIN_SERVER;
}
Then in my /Objective-C/ folder, I have some more files, once of which is called RestClientObj.m. In here, I have this lines of code:
NSString* url = [NSString stringWithFormat:#"%#/receipt/email/%#/%#/", [[RestClient getInstance] getServer], rrn, emailAddress];
NSString *authStr = [NSString stringWithFormat:#"%#:%#", [RestClient getUsername], [RestClient getPassword]];
So as you can see, I'm calling the RestClient.swift from here. The RestClientObj.h is as follows:
#ifndef RestClientObj_h
#define RestClientObj_h
#endif /* ResClientObj_h */
#interface RestClientObj : NSObject {
}
+(BOOL) sendSMS:(NSString *)rrn mn:(NSString *)mobileNumber;
+(BOOL) sendEmail:(NSString *)rrn mn:(NSString *)emailAddress;
#end
This whole upgrade is causing other problems too. I have another class with the following error:
No visible #interface for 'AppDelegate' declares the selector 'managedObjectContext'
on the line:
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext]; <-ERROR
Can anyone shed any light on this?
EDIT: Here's some code from the AppDelegate class:
lazy var managedObjectContext: NSManagedObjectContext? = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
if coordinator == nil {
return nil
}
var managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
Just to close this off.
The issue was missing the #obj identifier before the variable declaration to make it visible to my objective-c code, in combination with the XCode Swift Upgrade wizard renaming some functions.

FinderSync Extension - requestBadgeIdentifierForURL is never called

I have tested the template provided in Xcode for making a FinderSync Extension. Everything works well except two things:
a) The method requestBadgeIdentifierForURL is never called by the system when a folder is monitored so that badges are not set. What is going wrong here? I am right assuming that this method should be called when I e.g. move or scroll a monitored folder in Finder? By the way the methods beginObservingDirectoryAtURL and endObservingDirectoryAtURL are called properly in this context.
#import "FinderSync.h"
#interface FinderSync ()
#property NSURL *myFolderURL;
#end
#implementation FinderSync
- (instancetype)init {
self = [super init];
NSLog(#"%s launched from %# ; compiled at %s", __PRETTY_FUNCTION__, [[NSBundle mainBundle] bundlePath], __TIME__);
// Set up the directory we are syncing.
self.myFolderURL = [NSURL fileURLWithPath:#"/Users/hmaass/Downloads"];
[FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:self.myFolderURL];
// Set up images for our badge identifiers. For demonstration purposes, this uses off-the-shelf images.
[[FIFinderSyncController defaultController] setBadgeImage:[NSImage imageNamed: NSImageNameColorPanel] label:#"Status One" forBadgeIdentifier:#"One"];
[[FIFinderSyncController defaultController] setBadgeImage:[NSImage imageNamed: NSImageNameCaution] label:#"Status Two" forBadgeIdentifier:#"Two"];
return self;
}
#pragma mark - Primary Finder Sync protocol methods
- (void)beginObservingDirectoryAtURL:(NSURL *)url {
// The user is now seeing the container's contents.
// If they see it in more than one view at a time, we're only told once.
NSLog(#"beginObservingDirectoryAtURL:%#", url.filePathURL);
}
- (void)endObservingDirectoryAtURL:(NSURL *)url {
// The user is no longer seeing the container's contents.
NSLog(#"endObservingDirectoryAtURL:%#", url.filePathURL);
}
- (void)requestBadgeIdentifierForURL:(NSURL *)url {
NSLog(#"requestBadgeIdentifierForURL:%#", url.filePathURL);
// For demonstration purposes, this picks one of our two badges, or no badge at all, based on the filename.
NSInteger whichBadge = [url.filePathURL hash] % 3;
NSString* badgeIdentifier = #[#"", #"One", #"Two"][whichBadge];
[[FIFinderSyncController defaultController] setBadgeIdentifier:badgeIdentifier forURL:url];
}
#pragma mark - Menu and toolbar item support
- (NSString *)toolbarItemName {
return #"testfifi";
}
- (NSString *)toolbarItemToolTip {
return #"testfifi: Click the toolbar item for a menu.";
}
- (NSImage *)toolbarItemImage {
return [NSImage imageNamed:NSImageNameCaution];
}
- (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu {
// Produce a menu for the extension.
NSMenu *menu = [[NSMenu alloc] initWithTitle:#""];
[menu addItemWithTitle:#"Example Menu Item" action:#selector(sampleAction:) keyEquivalent:#""];
return menu;
}
- (IBAction)sampleAction:(id)sender {
NSURL* target = [[FIFinderSyncController defaultController] targetedURL];
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
NSLog(#"sampleAction: menu item: %#, target = %#, items = ", [sender title], [target filePathURL]);
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(#" %#", [obj filePathURL]);
}];
}
#end
b) I get the following message in the log console of Xcode when running the template above:
2015-08-25 15:33:00.300 testfifi[855:8134] Failed to connect
(colorGridView) outlet from (NSApplication) to
(NSColorPickerGridView): missing setter or instance variable
2015-08-25 15:33:00.300 testfifi[855:8134] Failed to connect (view)
outlet from (NSApplication) to (NSColorPickerGridView): missing setter
or instance variable 2015-08-25 15:33:00.321 testfifi[855:8134]
-[FinderSync init] launched from /Users/hmaass/Library/Developer/Xcode/DerivedData/testtest-egudnxkifjxirpbrjkohnatmjuro/Build/Products/Debug/testtest.app/Contents/PlugIns/testfifi.appex
; compiled at 20:38:18
Can someone help me to get rid of this message?
Thanks!
I already commented on your question but figured I should post a more complete answer.
It sounds like the issue you're having is another Finder Sync extension is "greedily" observing all folders, most likely the Dropbox Finder Integration. Try disabling all other Finder Sync extensions (under System Preferences -> Extensions -> Finder) and re-run your test.
If this resolves the issue, the problem is that Dropbox (or another app) has already called beginObservingDirectoryAtURL for the folder you're trying to monitor. Unfortunately, Apple's API is lacking in that there is no intelligent logic to who gets to monitor a folder when there are conflicting extensions. Currently, whichever Finder Sync extension starts first will "win".
Dropbox greedily monitors all folders under the user's home directory. I've written to both Apple and Dropbox to address this, but haven't heard any response. Currently, the (ugly) workaround I've implemented is to shutdown known "greedy" extensions, start my own extension, then restart the greedy extension.
Here's the workaround sample code for disabling "greedy" Finder Sync extensions. Nothing fancy, but it works.
(Adding this as a separate answer since it's really just a workaround and isn't necessarily the "correct" answer).
public static void main(String[] args) throws Exception {
String[] greedyFSProcessNames =
new String[] { "com.getdropbox.dropbox.garcon" };
List<String> disabledGreedyFSProcessNames = new ArrayList<>();
for (String greedyFSProcessName : greedyFSProcessNames) {
if (!_isFSProcessRunning(greedyFSProcessName)) {
continue;
}
_enableFSProcess(greedyFSProcessName, false);
disabledGreedyFSProcessNames.add(greedyFSProcessName);
}
_enableFSProcess("com.dejuknow.myfindersync", true);
for (String disabledGreedyFSProcessName :
disabledGreedyFSProcessNames) {
_enableFSProcess(disabledGreedyFSProcessName, true);
}
}
private static boolean _isFSProcessRunning(String processName)
throws Exception {
BufferedReader bufferedReader = null;
try {
Process process = Runtime.getRuntime().exec(
"pluginkit -m -i" + processName);
bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line = null;
while ((line = bufferedReader.readLine()) != null) {
if (line.startsWith("+")) {
return true;
}
else {
return false;
}
}
}
finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
return false;
}
private static void _enableFSProcess(String processName, boolean enable)
throws Exception {
String electionArgument = null;
if (enable) {
electionArgument = "use";
}
else {
electionArgument = "ignore";
}
String[] arguments = new String[] {
"pluginkit", "-e", electionArgument, "-i", processName
};
while (_isFSProcessRunning(processName) != enable) {
Process process = Runtime.getRuntime().exec(arguments);
process.waitFor();
Thread.sleep(100);
}
}
The folders I know Dropbox uses: ~/Dropbox, ~/Documents and ~/Desktop.
I have a FinderSync app too and I can display badges on all of my folders except those. Fortunately the context menu does not seem to suffer any conflict, both extension's menu items are shown.

How BarAlarm iOS app is made? [duplicate]

I am developing a non-appstore app for iOS. I want to read the cellular signal strength in my code.
I know Apple doesn't provide any API by which we can achieve this.
Is there any private API that can be used to achieve this? I have gone through the various threads regarding this issue but could not find any relevant info.
It is completely possible because there is an app in the app-store for detecting the carrier's signal strength.
Get signalStreght IOS9:
UIApplication *app = [UIApplication sharedApplication];
NSArray *subviews = [[[app valueForKey:#"statusBar"] valueForKey:#"foregroundView"] subviews];
NSString *dataNetworkItemView = nil;
for (id subview in subviews) {
if([subview isKindOfClass:[NSClassFromString(#"UIStatusBarSignalStrengthItemView") class]])
{
dataNetworkItemView = subview;
break;
}
}
int signalStrength = [[dataNetworkItemView valueForKey:#"signalStrengthRaw"] intValue];
NSLog(#"signal %d", signalStrength);
It is not very hard.
Link CoreTelephony.framework in your Xcode project
Add the following lines where you need it
Code:
int CTGetSignalStrength(); // private method (not in the header) of Core Telephony
- (void)aScanMethod
{
NSLog(#"%d", CTGetSignalStrength()); // or do what you want
}
And you are done.
Update May 2016
Apple removed this opportunity.
I briefly looked at the VAFieldTest project located at Github.
There seems to be getSignalStrength() and register_notification() functions in Classes/VAFieldTestViewController.m that might be interesting to you as they call into CoreTelephony.framework.
I am pretty confident that some of the used calls are undocumented in the CoreTelephony framework documentation from Apple and therefore private - any app in the AppStore must have slipped passed inspection.
To get signal streght in iOS 9 or above in Swift 3, without using the private API from CoreTelephony - CTGetSignalStrength(). Just scouring the statusBar view.
func getSignalStrength() -> Int {
let application = UIApplication.shared
let statusBarView = application.value(forKey: "statusBar") as! UIView
let foregroundView = statusBarView.value(forKey: "foregroundView") as! UIView
let foregroundViewSubviews = foregroundView.subviews
var dataNetworkItemView:UIView!
for subview in foregroundViewSubviews {
if subview.isKind(of: NSClassFromString("UIStatusBarSignalStrengthItemView")!) {
dataNetworkItemView = subview
break
} else {
return 0 //NO SERVICE
}
}
return dataNetworkItemView.value(forKey: "signalStrengthBars") as! Int
}
Attention: If the status bar is hidden, the key "statusBar" will return nil.
I haven't tested it, but apparently this is now a method of CTTelephonyNetworkInfo instead of a global/static function.
The return type is id, so I think you get either a NSDictionary (as the _cachedSignalStrength ivar implies) or an NSNumber (as the old function implies).
id signalStrength = [[CTTelephonyNetworkInfo new] signalStrength];
This changed in iOS 8.3, as you can see from the commit.
Note that this is still not documented! So if your app will go in the App Store, take your precautions.
Here's Lucas' answer converted to Xamarin, and tested on iOS 10.2.1:
var application = UIApplication.SharedApplication;
var statusBarView = application.ValueForKey(new NSString("statusBar")) as UIView;
var foregroundView = statusBarView.ValueForKey(new NSString("foregroundView")) as UIView;
UIView dataNetworkItemView = null;
foreach (UIView subview in foregroundView.Subviews)
{
if ("UIStatusBarSignalStrengthItemView" == subview.Class.Name)
{
dataNetworkItemView = subview;
break;
}
}
if (null == dataNetworkItemView)
return false; //NO SERVICE
int bars = ((NSNumber)dataNetworkItemView.ValueForKey(new NSString("signalStrengthBars"))).Int32Value;

How can I find calls to UIKit instances from a secondary thread?

My app is crashing in iOS 5 because I have some code that is calling UIKit instances from a secondary thread. You know you have this problem when you see the following error:
bool _WebTryThreadLock(bool), 0x811bf20: Multiple locks on web thread not allowed! Please file a bug. Crashing now…
So my question is what are some ways that I can find the code that is calling the UIKit instances from a secondary thread?
Here are some things I’ve tried already:
Commented out blocks that could be violating the rule
Added assert([NSThread isMainThread]) in places that might be processing in secondary thread
Added a symbolic breakpoint for _WebTryThreadLock
These things have helped me to find problem areas. However, in my final crash the _WebTryThreadLock breakpoint has no stack trace in any of the other threads. So, how I can find the code that causing the problem without a stack trace?
Thanks for your time!
Your assert() is probably the most valuable tool in this. I've been known to put a similar assertion at the beginning of every method in my Controller classes. If that doesn't find it, I add the assertion to my View classes. If that doesn't find it, I add it to any Model classes that I think are main-thread only.
To #craig's comment, the fact that it claims to be an internal bug might be accurate. But I think you're on the right path to closely examine your own code first.
I adapted the PSPDFUIKitMainThreadGuard.m to allow one to not have to worry about these things. Here: https://gist.github.com/k3zi/98ca835b15077d11dafc :
#import <objc/runtime.h>
#import <objc/message.h>
// Compile-time selector checks.
#define PROPERTY(propName) NSStringFromSelector(#selector(propName))
// A better assert. NSAssert is too runtime dependant, and assert() doesn't log.
// http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html
// Accepts both:
// - PSPDFAssert(x > 0);
// - PSPDFAssert(y > 3, #"Bad value for y");
#define PSPDFAssert(expression, ...) \
do { if(!(expression)) { \
NSLog(#"%#", [NSString stringWithFormat: #"Assertion failure: %s in %s on line %s:%d. %#", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSString stringWithFormat:#"" __VA_ARGS__]]); \
abort(); }} while(0)
///////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Helper for Swizzling
BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
PSPDFAssert(c && origSEL && newSEL && block);
Method origMethod = class_getInstanceMethod(c, origSEL);
const char *encoding = method_getTypeEncoding(origMethod);
// Add the new method.
IMP impl = imp_implementationWithBlock(block);
if (!class_addMethod(c, newSEL, impl, encoding)) {
NSLog(#"Failed to add method: %# on %#", NSStringFromSelector(newSEL), c);
return NO;
}else {
// Ensure the new selector has the same parameters as the existing selector.
Method newMethod = class_getInstanceMethod(c, newSEL);
PSPDFAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, #"Encoding must be the same.");
// If original doesn't implement the method we want to swizzle, create it.
if (class_addMethod(c, origSEL, method_getImplementation(newMethod), encoding)) {
class_replaceMethod(c, newSEL, method_getImplementation(origMethod), encoding);
}else {
method_exchangeImplementations(origMethod, newMethod);
}
}
return YES;
}
// This installs a small guard that checks for the most common threading-errors in UIKit.
// This won't really slow down performance but still only is compiled in DEBUG versions of PSPDFKit.
// #note No private API is used here.
__attribute__((constructor)) static void PSPDFUIKitMainThreadGuard(void) {
#autoreleasepool {
for (NSString *selStr in #[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) {
SEL selector = NSSelectorFromString(selStr);
SEL newSelector = NSSelectorFromString([NSString stringWithFormat:#"pspdf_%#", selStr]);
if ([selStr hasSuffix:#":"]) {
PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self, CGRect r) {
if(!NSThread.isMainThread){
dispatch_async(dispatch_get_main_queue(), ^{
((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
});
}else{
((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
}
});
}else {
PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self) {
if(!NSThread.isMainThread){
dispatch_async(dispatch_get_main_queue(), ^{
((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
});
}else
((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
});
}
}
}
}
It automatically kicks calls into the main thread and thus you wouldn't even have to do anything but plop the code in.
This problem comes because you want to access to UI from secondary thread somehow, it can from webview of whatever else. It is not permitted because UIKit is not thread safe and can be accessed only from MainThread.
The very first thing you can do is to change your thread call to [self performSelectorOnMainThread:#selector(myMethod) withObject:nil waitUntilDone:NO]; (look for documentation).
In case when you have no other choice you can use GCD(Grand Central Dispathc)...
This code (just add to project and compile this file without ARC) causes assertions on UIKit access outside the main thread: https://gist.github.com/steipete/5664345
I've just used it to pickup numerous UIKit/main thread issues in some code I've just picked up.