2018年12月14日 星期五

better way 更新tableview

當你一個小改變要更新cell的時候,reloadData 會導致很大的效能浪費,更慘的是tableview會有不必要的動畫被看到。

有一種解法是放delegate去給tableviewcell執行,這樣不用重繪tableview的整個cell,但還是會給所有的cell執行一次。

更好的作法是先更新tableview data,但不去reloadData,然後只更新看得見的部份
for (EmployeeListTableViewCell *cell in self.tableView.visibleCells) {
            //TODO: update the UI
}
剩下的交給tableView自己,在滑動到其他cell的時候更新。

2018年11月6日 星期二

UIApplicationWillChangeStatusBarOrientationNotification v.s. UIApplicationDidChangeStatusBarOrientationNotification


結論:
UIApplicationWillChangeStatusBarOrientationNotification
會先被呼叫,而且userInfo 是傳送將要轉換的 orientation。

UIApplicationDidChangeStatusBarOrientationNotification
後被呼叫,但userInfo是傳送轉換前的 orientation。

code:

    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(statusOrientationWillChange:)
                                               name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
   
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(statusOrientationDidChange:)
                                               name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];

- (void)statusOrientationWillChange:(NSNotification *)notification {
    if (notification.userInfo != nil && notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] != nil) {
        UIInterfaceOrientation orientation = (UIInterfaceOrientation)[(NSNumber *)notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] integerValue];
        if (UIInterfaceOrientationIsLandscape(orientation)) {
            self.isLandscape = YES;
        } else {
            self.isLandscape = NO;
        }
    }
   
    [self.tableView reloadData];
}

- (void)statusOrientationDidChange:(NSNotification *)notification {
    if (notification.userInfo != nil && notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] != nil) {
        UIInterfaceOrientation orientation = (UIInterfaceOrientation)[(NSNumber *)notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] integerValue];
        if (UIInterfaceOrientationIsLandscape(orientation)) {
            self.isLandscape = YES;
        } else {
            self.isLandscape = NO;
        }
    }
   
}

Strange thing: A - 2 > 0 is not equvalent to A > 2

(lldb) po self.customFields.count - 2 > 0
true

(lldb) po self.customFields.count > 2
false

(lldb) po self.customFields
<__NSArrayM 0x108e54540>(

)

(lldb) po self.customFields.count
<nil>

2018年11月2日 星期五

reuse custom tableview cell

當你用dynamic style cell的時候
所有關於cell的改變都要基於cell source
所以最好的方式就是直接create a Class for table view source
讓整個改動都變得有彈性
不要直接去改當下那個cell,那是不合邏輯的,而且你會修bug修不完
例如,你從coredate的一個表中拿到資料,不要直接拿來用
創一個class去包它,這個class可以自由加入你要的其他設定給你的custom table cell

2018年10月18日 星期四

提早拿到constraint後的frame

加入viewDidLoad可以拿到autolayout後的正確frame

加在animation中,可以在autolayout的情況下作用
[UIView animateWithDuration:0.5 animations:^{
            self.filterViewWidthConstraint.constant = 200;
            [self.view layoutIfNeeded];
        }];

用pdf取代png

在asset中放入pdf,設成all scale,可以用來取代svg當作向量圖。

如果是icon的話,還可以設成templete,可以用tintColor來改變icon的顏色。

超級實用的小技巧

2018年10月15日 星期一

Add touch began to pan gesture

!!!!!!一定要做#import "UIPanGestureRecognizerSubclass.h"
然後用category或是subclass去設定這個state,
才能用readwrite不然,原本的recognizer是readonly










@interface TouchableImageView:UIImageView
@property(strong, nonatomic) UIPanGestureRecognizer *panGR; // 這裡要改成自己的recognizer或是實作一個category
@end

@implementation TouchableImageView
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.panGR.state = UIGestureRecognizerStateBegan;
}
@end
//直接改狀態就能trigger gesture的began!!

2018年9月24日 星期一

用自己的dependency專案到cocoapods中

我送了一個pull request 到starscream,增加了支援http proxy的功能,
但遲遲沒有人審核,這時只好在專案中使用用自己的github上的版本。
問題是startcream只是Socket.IO-clinet這個專案的dependent project,
而Podfile不支援更來url來使用你自己project來作為其他專案dependent project。

所以我必須要在podfile中取消dependency再把二個專案都加入pods
建好xcworkspace檔案之後,再手動修改pod.xcodeproj來連Socket.IO-clinet使用starscream來build
  1. 去Socket.IO-clinet的project
  2. 去Build Phases
  3. 在Target Dependencies 中加入starscream
  4. 在Link Binary With Libraries 加入 starscream
  5. 最重要的是在Build Settings中把Framework Search Paths中把starscream的位置"$PODS_CONFIGURATION_BUILD_DIR/Starscream"加進去 (記得要在上面選到All才看得到這個設定)
P.S.
另一個方式是把starscream設為private pod,然後用podfile就可以取得。

2018年7月1日 星期日

predicateWithFormat在比對NSNumber的坑

環境: iOS SDK9
在你想比對NSNumber時,從JSON轉出來的數值出現了NSTaggedPointerString,你會比對不到,於是得到nil.
- (Visitor *)visitorByID:(NSNumber *)uid {
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:[NSEntityDescription entityForName:@"Visitor" inManagedObjectContext:self.managedObjectContext]];
   
    if ([uid isKindOfClass:[NSNumber class]]) {
        [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"self.visitor_id == %@", uid]];
    } else {
        NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
        NSNumber *numUid = [numberFormatter numberFromString:(NSString *)uid];
        [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"self.visitor_id == %@", numUid]];
    }
       
        NSError * error = nil;
        NSArray * result = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
       
        if (error!=nil) {
            SOLogError(@"%s[%d:%s]: %@", __FILE__, __LINE__, __FUNCTION__,error.localizedDescription);
        }
}

解法:
把NSTaggedPointerString再轉回NSNumber一次
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
NSNumber *numUid = [numberFormatter numberFromString:(NSString *)uid];

CoreData的performBlock和performBlockAndWait的差異

performBlock
會異步執行在context的thread之中(不會阻塞當前的thread)


performBlockAndWait


會同步執行在context的thread之中(會阻塞當前的thread,直到執行完block中的code再返回)


NSManagedObjectContext save()

https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext/1506866-save
https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext/1506954-haschanges


2018年6月28日 星期四

[array copy] 和 [NSArray arrayWithArray:array] 的異同

// https://segmentfault.com/q/1010000003854902
如果arrayimmutable类型的NSArray
  • copy只是retain,没有创建新对象
  • arrayWithArray 创建了新的NSArray对象,并将原有数组元素填充进去,数组元素还是原来的对象
如果arraymutable类型的NSArray
  • 二者最终结果是等效的,创建了新的NSArray对象,但数据元素还是原来的对象
如果array == nil
  • copy的结果是nil
  • arrayWithArray 结果是长度为0的NSArray对象
无论哪种情况,数组元素都是共享的 不会被复制

2018年6月12日 星期二

nslog to file

- (void)redirectLogToDocuments {
    NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [allPaths objectAtIndex:0];
    NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:@"logFile.txt"];
  
    if ([[NSFileManager defaultManager] fileExistsAtPath:pathForLog]) {
        NSError *error;
        [[NSFileManager defaultManager] removeItemAtPath:pathForLog error:&error];
        if (error!=nil) {
            NSLog(@"%@", error.localizedDescription);
        }
    }
    freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
}

讓圖示在AttributedText上下置中


https://stackoverflow.com/questions/26105803/center-nstextattachment-image-next-to-single-line-uilabel




let iconImage = UIImage(named: "icon.png")!
var icon = NSTextAttachment()
icon.bounds = CGRect(x: 0, y: (titleFont.capHeight - iconImage.size.height).rounded() / 2, width: iconImage.size.width, height: iconImage.size.height)
icon.image = iconImage
let iconString = NSAttributedString(attachment: icon)
titleText.append(iconString)

2018年6月8日 星期五

rate the app

- (IBAction)rateButtonTapped:(UIButton *)sender {
    if (@available(iOS 10.3, *)) {
        [SKStoreReviewController requestReview];
    } else {
        NSURL *url = [NSURL URLWithString:@"itms-apps://itunes.apple.com/tw/app/swipedon-visitor-management/id986185962?l=en&mt=8"];
        [[UIApplication sharedApplication] openURL:url];
    }
}

2018年6月5日 星期二

用CoreData存圖片

不要用core data存圖
是可以轉存NSData去存,但只要你的ManagedObjectContext沒有釋放,那麼那張圖就不會被釋放,如果圖片大一點,一下就爆了

用core data存imageName,把圖片存到文件資料夾裡,用imageName去對應,
並且監聽imageName的setter,在改名之前先刪去原來的圖。

2018年5月29日 星期二

block中的self

Medium上有一篇文章說用_variable取代self.variable 就不會循環引用
實驗之後發現:
_variable也會
還是宣告成weak最好

既使weakSelf去呼叫一個方法,方法裡面有不是weak的self,那還是不會循環引用的。

用變數型態直接宣告在.h或.m,就算加了__weak還是一樣會循環引用,得要改成property的型態並宣告成weak才能避免

如果你在block中使用一些singleton例如[UIApplication sharedApplication],只要不要用變數去接它,就可以安全下莊

2018年5月25日 星期五

WKWebView

// Inject新的attribute給你的wkwebview
- (void)setupWebView {
    NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('initial-scale', '1'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
  
    WKUserScript *userScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    [userContentController addUserScript:userScript];
  
    WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
    wkWebConfig.userContentController = userContentController;
  
// 因為pdf的高度用webview的寬度去調整,所以要先設定好。
    CGRect initialWebViewFrame = self.view.frame;
    if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait ||
         [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown) {
        CGFloat height = initialWebViewFrame.size.height;
        CGFloat width = initialWebViewFrame.size.width;
        if (height > width) {
            initialWebViewFrame.size = CGSizeMake(width, 0);
        } else {
            initialWebViewFrame.size = CGSizeMake(height, 0);
        }
    }
    _webView = [[WKWebView alloc] initWithFrame:initialWebViewFrame configuration:wkWebConfig];
    _webView.scrollView.contentSize = initialWebViewFrame.size;
    [_webViewContainer addSubview:_webView];
    _webView.navigationDelegate = self;
    [_webViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:_webView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_webViewContainer attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
    [_webViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:_webView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_webViewContainer attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];
    [_webViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:_webView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:_webViewContainer attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];
    [_webViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:_webView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:_webViewContainer attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0]];
  
    _webView.scrollView.scrollEnabled = NO;
    [_webView.scrollView setBackgroundColor:[UIColor clearColor]];
    [_webView setBackgroundColor:[UIColor clearColor]];
    [_webView setOpaque:NO];
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    if ([[SettingsManager sharedManager].agreement[@"agreement_type"] isEqualToString:@"pdf"]) {

    } else {
        if (shouldReloadFlag) {
            shouldReloadFlag = NO;
            [self loadWebView];
        }
    }
    [self resizeScrollView];
}

- (void)resizeScrollView {
    if ([[SettingsManager sharedManager].agreement[@"agreement_type"] isEqualToString:@"pdf"]) {
        if (_webView.isLoading == false) {
            for (UIView *subview in self.webView.scrollView.subviews) {
                subview.backgroundColor = [UIColor clearColor];

// 取得pdf的高
                if ([[[subview class] description] isEqualToString:@"WKPDFView"]) {
                    NSLog(@"pdfview.size: %@ webviewContainer.size:%@", NSStringFromCGSize(subview.frame.size), NSStringFromCGSize(_webViewContainer.frame.size));
                    [self resizeScrollViewByWebViewContentHeight:subview.frame.size.height / subview.frame.size.width * _webViewContainer.frame.size.width];
                    break;
                }
            }
        }
    } else {
        if (_webView.isLoading == false) {
            __weak typeof(self) weakSelf = self;
            [_webView evaluateJavaScript:@"document.body.offsetHeight" completionHandler:^(id result, NSError * _Nullable error) {
                CGFloat height = [result floatValue] + 30.0;
                [weakSelf resizeScrollViewByWebViewContentHeight:height];
            }];
        }
    }
}

2018年5月19日 星期六

keyboard disappaer when tableView scrolling

筆記一下這個坑:
在前人的code裡一直找不到endEditing or resignFirstResponder 寫在那裡,但每次一動tableview小鍵盤就消失,最後才找到是在storyboard裡面被設定了


如果要手動設定的話,是下面這個property
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; 

2018年4月25日 星期三

獲取UIWebView的內容高度

// 直接抓
CGSize newSize = [webView.scrollView contentSize];
webViewHeightConstraint.constant = newSize.height;

// 如果是單純本地端的html,這樣比較準
CGFloat webViewHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.offsetHeight"] floatValue];
textViewHeightConstraint.constant = webViewHeight;

2018年4月18日 星期三

設定用UIModalPresentationFormSheet推出之後的視窗尺寸

categoriesViewController.preferredContentSize = CGSizeMake(440.0, 320.0);
categoriesViewController.modalPresentationStyle = UIModalPresentationFormSheet;

2018年4月13日 星期五

get mode name

#import <sys/utsname.h>

    struct utsname systemInfo;
    uname(&systemInfo);
    NSString *currentDeviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];

2018年3月27日 星期二

存取層級(swift3.0以上適用)

  1. open:可以在module外面被使用,也可以被override。
  2. public:可以在module外面被使用,不可以被override。
  3. internal:不可以在module外面被使用。
  4. fileprivate:只可以在同一個file中所定義的class間使用。
  5. private:只可以在宣告它的class中使用,不被其他class存取。

2018年2月28日 星期三

flip image horizontally 左右翻轉圖片

UIImage *flippedImage = [UIImage imageWithCGImage: yourImage.CGImage
                                                scale:trimmedImage.scale
                                          orientation:UIImageOrientationUpMirrored];

orientation 螢幕方向

// 這個是裝置的實際方向
[UIDevice currentDevice].orientation

// 這個是螢幕的方向(如果你鎖住一些方向,他就不會顯示那些方向)
[UIApplication sharedApplication].statusBarOrientation


// 旋轉螢幕時的callback
 override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(deviceRotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    func deviceRotated(){
        if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
            print("Landscape")
            // Resize other things
        }
        if UIDeviceOrientationIsPortrait(UIDevice.current.orientation) {
            print("Portrait")
            // Resize other things
        }
    }

2018年2月20日 星期二

nested preprocessor ifdef

#ifdef PRODUCTION
    [buildInfoLabel setText:@""];
#else
#ifdef App_Sentry_Environment
    [buildInfoLabel setText:App_Sentry_Environment];
#endif
#endif

2018年2月19日 星期一

Compiler Warning messages


no rule to process file of type net.daringfireball.markdown for architecture x86_64
  1. Select Project Navigator
  2. Select your project
  3. Select your target
  4. Select Build Phases
  5. Move files (in this case, CHANGLELOG.md) that you don't want the compiler to process from Compile Sources to Copy Bundle Resources
     
Property access result unused - getters should not be used for side effects
  1. self.lables.removeLastObject; 方法不要用. 要用[],不然compiler會認為是getter
  2. [self.lables removeLastObject];
Implicit conversion from enumeration type 'SO_LOG_LEVEL' to different enumeration type 'DDLogFlag' (aka 'enum DDLogFlag')
  1.  LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, level, 0, nil, __PRETTY_FUNCTION__, message); 要先轉好型再放進去
  2.  LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, (DDLogFlag)level, 0, nil, __PRETTY_FUNCTION__, message);
Format string is not a string literal (potentially insecure) 
  1. LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, (DDLogFlag)level, 0, nil, __PRETTY_FUNCTION__, message); 參數是是用Format String不是普通String
  2. LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, (DDLogFlag)level, 0, nil, __PRETTY_FUNCTION__, @"%@", message);
  
PerformSelector may cause a leak because its selector is unknown
  1. 因為可能會找不到function所以使用selector可能會有leak,這裡我們直接忽略它 
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector: aSelector];
    #pragma clang diagnostic pop
     
warning: Consistency Error: Setting the No Action Delete Rule on EmployeeQueue.child may result in relationships to deleted objects


Fixed width constraints may cause clipping
  1. 這是Localization Issue,Xcode9擔心我們的文字輸入框在固定寛度的情況下,文字過長時會被clip掉,你可以用它推薦的解法,把trailing或leading設為>=或<=現在的寬度,也可以移除這個constrains
  2. 如果是textField你不會想在使用者輸入太長的時候延長它,你可以把width改成依container的寬度比例來work around.

2018年2月16日 星期五

版本號判斷

Starting Xcode 9, in Objective-C:
if (@available(iOS 11, *)) {
    // iOS 11 (or newer) ObjC code
} else {
    // iOS 10 or older code
}
Starting Xcode 7, in Swift:
if #available(iOS 11, *) {
    // iOS 11 (or newer) Swift code
} else {
    // iOS 10 or older code
}
 

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 // iOS 11

#else

#endif 




如果你是用10.3的SDK,那你就會看到下面的設定 (在10.3SDK中的AvailabilityInternal.h
/* make sure a default max version is set */
    #ifndef __IPHONE_OS_VERSION_MAX_ALLOWED
        #define __IPHONE_OS_VERSION_MAX_ALLOWED     __IPHONE_10_3
    #endif


#define __IPHONE_2_0      20000
#define __IPHONE_2_1      20100
#define __IPHONE_2_2      20200
#define __IPHONE_3_0      30000
#define __IPHONE_3_1      30100
#define __IPHONE_3_2      30200
#define __IPHONE_4_0      40000
#define __IPHONE_4_1      40100
#define __IPHONE_4_2      40200
#define __IPHONE_4_3      40300
#define __IPHONE_5_0      50000
#define __IPHONE_5_1      50100
#define __IPHONE_6_0      60000
#define __IPHONE_6_1      60100
#define __IPHONE_7_0      70000
#define __IPHONE_7_1      70100
#define __IPHONE_8_0      80000
#define __IPHONE_8_1      80100
#define __IPHONE_8_2      80200
#define __IPHONE_8_3      80300
#define __IPHONE_8_4      80400
#define __IPHONE_9_0      90000
#define __IPHONE_9_1      90100
#define __IPHONE_9_2      90200
#define __IPHONE_9_3      90300
#define __IPHONE_10_0    100000
#define __IPHONE_10_1    100100
#define __IPHONE_10_2    100200
#define __IPHONE_10_3    100300
/* __IPHONE_NA is not defined to a value but is uses as a token by macros to indicate that the API is unavailable */

2018年1月17日 星期三

模仿拍完照的閃現效果

self.view.alpha = 0.0;
                                                                    [UIView animateWithDuration: 0.5
                                                                                     animations: ^{
                                                                                         self.view.alpha = 1.0;
                                                                                     }
                                                                                     completion: ^(BOOL finished) {
                                                                                     }
                                                                     ];

2018年1月12日 星期五

在moveRowAtIndexPath動畫之後加入callback

[CATransaction begin];
    [CATransaction setCompletionBlock: ^{
        // Code to be executed upon completion
        [self connectPrinterWithDeviceInfo:deviceInfo];
    }];
    [tableView beginUpdates];
    [tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
    [tableView endUpdates];
    [CATransaction commit];

2018年1月9日 星期二

在段落中某些特定文字加入連結

  • 只有UITextView可以做到。
  • 要實作UITextViewDelegate
self.linkTextView.delegate = self; 

#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(nonnull NSURL *)URL inRange:(NSRange)characterRange {
     //可以做些判斷
    return YES;
}

  • 加入某段文字 
NSString *str = @"For help please visit our Knowledge base";
    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:str];
    [attrStr addAttribute:NSLinkAttributeName value:[NSURL URLWithString:@"https://yourUrl.com"] range:[str rangeOfString:@"Knowledge base"]];
    self.linkTextView.attributedText = attrStr;
  
  • 整段文字
NSDictionary *dictAttr = @{NSLinkAttributeName:[NSURL URLWithString:@"http://yourUrl.com"]};
    NSAttributedString *attrStr = [[NSAttributedString alloc]initWithString:@"please visit our Knowledge base" attributes:dictAttr];
    self.linkTextView.attributedText = attrStr;

2018年1月3日 星期三

去特定的setting頁

// wifi settings 
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"App-Prefs:root=WIFI"]];
 
// Bluetooth settings 
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"App-Prefs:root=Bluetooth"]];