Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modify the KVO function #36

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions Demo/Base.lproj/LaunchScreen.storyboard
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11524"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
Expand All @@ -13,15 +18,21 @@
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Q7N-eY-a8e">
<rect key="frame" x="160" y="617" width="55" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Remove"/>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
<point key="canvasLocation" x="-57" y="87"/>
</scene>
</scenes>
</document>
78 changes: 72 additions & 6 deletions Demo/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,25 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina5_5" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11524"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="pKd-Yi-7XS">
<rect key="frame" x="20" y="20" width="374" height="200"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="200" id="bLq-sD-wlC"/>
</constraints>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DuC-E4-FCK">
<rect key="frame" x="40" y="250" width="98" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="wHE-BG-d7C"/>
</constraints>
<state key="normal" title="Change Font"/>
<connections>
<action selector="onChangeFont:" destination="BYZ-38-t0r" eventType="touchUpInside" id="wVz-B6-im2"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Obc-BH-hIM">
<rect key="frame" x="158" y="250" width="98" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="eHG-IF-BiM"/>
</constraints>
<state key="normal" title="Change Color"/>
<connections>
<action selector="onChangeColor:" destination="BYZ-38-t0r" eventType="touchUpInside" id="yTi-c7-xmH"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c3X-Um-ntK">
<rect key="frame" x="276" y="250" width="98" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="ahJ-sW-GLH"/>
</constraints>
<state key="normal" title="Test Release"/>
<connections>
<action selector="onTextRemove:" destination="BYZ-38-t0r" eventType="touchUpInside" id="7Q4-5i-EOL"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="DuC-E4-FCK" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="20" id="2ui-Bf-T6Z"/>
<constraint firstItem="Obc-BH-hIM" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="230" id="54M-E0-lTh"/>
<constraint firstItem="DuC-E4-FCK" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="230" id="6gc-XM-SjC"/>
<constraint firstItem="pKd-Yi-7XS" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="8mX-wt-9b3"/>
<constraint firstItem="c3X-Um-ntK" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="230" id="KuS-2Y-Tcc"/>
<constraint firstItem="pKd-Yi-7XS" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="Ltu-lm-HUL"/>
<constraint firstAttribute="trailingMargin" secondItem="c3X-Um-ntK" secondAttribute="trailing" constant="20" id="TE9-dB-ckx"/>
<constraint firstItem="pKd-Yi-7XS" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="UUX-6W-dqv"/>
<constraint firstItem="DuC-E4-FCK" firstAttribute="width" secondItem="Obc-BH-hIM" secondAttribute="width" id="ggf-Dl-81O"/>
<constraint firstItem="Obc-BH-hIM" firstAttribute="leading" secondItem="DuC-E4-FCK" secondAttribute="trailing" constant="20" id="jnV-XC-Lia"/>
<constraint firstItem="c3X-Um-ntK" firstAttribute="leading" secondItem="Obc-BH-hIM" secondAttribute="trailing" constant="20" id="nLw-rn-lqE"/>
<constraint firstItem="Obc-BH-hIM" firstAttribute="width" secondItem="c3X-Um-ntK" secondAttribute="width" id="qos-ne-z5G"/>
</constraints>
</view>
<connections>
<outlet property="textView" destination="pKd-Yi-7XS" id="YOv-pt-Kkb"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-386.95652173913044" y="-200.54347826086959"/>
</scene>
</scenes>
</document>
31 changes: 24 additions & 7 deletions Demo/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,38 @@
#import "ViewController.h"
#import <UITextView_Placeholder/UITextView+Placeholder.h>

@interface ViewController()

@property (weak, nonatomic) IBOutlet UITextView *textView;


@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

UITextView *textView = [[UITextView alloc] init];
textView.frame = CGRectMake(0, 20, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));
textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
textView.placeholder = @"Are you sure you don\'t want to reconsider? Could you tell us why you wish to leave StyleShare? Your opinion helps us improve StyleShare into a better place for fashionistas from all around the world. We are always listening to our users. Help us improve!";
self.textView.placeholder = @"Are you sure you don\'t want to reconsider? Could you tell us why you wish to leave StyleShare? Your opinion helps us improve StyleShare into a better place for fashionistas from all around the world. We are always listening to our users. Help us improve!";
self.textView.placeholderLabel.textAlignment = NSTextAlignmentCenter;
NSDictionary *attrs = @ {
NSFontAttributeName: [UIFont boldSystemFontOfSize:20],
};
textView.attributedText = [[NSAttributedString alloc] initWithString:@"Hi" attributes:attrs];
textView.font = [UIFont systemFontOfSize:15];
[self.view addSubview:textView];
self.textView.attributedText = [[NSAttributedString alloc] initWithString:@"Hi" attributes:attrs];
self.textView.font = [UIFont systemFontOfSize:15];
[self.view addSubview:self.textView];
}

- (IBAction)onTextRemove:(id)sender {
[self.textView removeFromSuperview];
}

- (IBAction)onChangeFont:(id)sender {
self.textView.font = [UIFont systemFontOfSize:30];
}

- (IBAction)onChangeColor:(id)sender {
self.textView.textColor = [UIColor redColor];
}

@end
136 changes: 82 additions & 54 deletions Sources/UITextView+Placeholder.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,89 @@
#import <objc/runtime.h>
#import "UITextView+Placeholder.h"

@implementation UITextView (Placeholder)
#pragma mark - `observingKeys`

#pragma mark - Swizzle Dealloc
@interface DWTextViewEventObserver : NSObject

@property (copy, nonatomic) void (^execution)(BOOL fontChanged);
// can't use weak, because weak before UITextView object dealloc, it will set all weak reference to nil
// it will make our removeObserver failed to remove.
@property (unsafe_unretained, nonatomic) UITextView *target;

@end

@implementation DWTextViewEventObserver

+ (void)observe:(UITextView *)target then:(void (^)(BOOL fontChanged))exec {
DWTextViewEventObserver *monitor = [[DWTextViewEventObserver alloc] init];
monitor.execution = exec;
monitor.target = target;
[monitor observeEvents];
int randomKey;
// It is true that swizzle method of dealloc in NSObject Category can do the same thing, but that will cause method polluted!
objc_setAssociatedObject(target, &randomKey, monitor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (void)load {
// is this the best solution?
method_exchangeImplementations(class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc")),
class_getInstanceMethod(self.class, @selector(swizzledDealloc)));
#pragma mark - LifeCycle
+ (NSArray *)observingKeys {
return @[@"attributedText",
@"bounds",
@"font",
@"frame",
@"text",
@"textAlignment",
@"textContainerInset"];
}

- (void)swizzledDealloc {
- (void)dealloc {
[self releaseObserveKeys];
}

- (void)observeEvents {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updatePlaceholderLabel)
name:UITextViewTextDidChangeNotification
object:self.target];

for (NSString *key in self.class.observingKeys) {
[self.target addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
}
}

- (void)updatePlaceholderLabel {
if (self.execution) {
self.execution(NO);
}
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (self.execution) {
BOOL updateFont = [keyPath isEqualToString:@"font"] && change[NSKeyValueChangeNewKey] != nil;
self.execution(updateFont);
}
}

- (void)releaseObserveKeys {
[[NSNotificationCenter defaultCenter] removeObserver:self];
UILabel *label = objc_getAssociatedObject(self, @selector(placeholderLabel));
if (label) {
for (NSString *key in self.class.observingKeys) {
@try {
[self removeObserver:self forKeyPath:key];
}
@catch (NSException *exception) {
// Do nothing
}
for (NSString *key in self.class.observingKeys) {
@try {
[self.target removeObserver:self forKeyPath:key context:nil];
}
@catch (NSException *exception) {
// Do nothing
}
}
[self swizzledDealloc];
}

@end

@implementation UITextView (Placeholder)

#pragma mark - Swizzle Dealloc

#pragma mark - Class Methods
#pragma mark `defaultPlaceholderColor`
Expand All @@ -64,20 +121,6 @@ + (UIColor *)defaultPlaceholderColor {
return color;
}


#pragma mark - `observingKeys`

+ (NSArray *)observingKeys {
return @[@"attributedText",
@"bounds",
@"font",
@"frame",
@"text",
@"textAlignment",
@"textContainerInset"];
}


#pragma mark - Properties
#pragma mark `placeholderLabel`

Expand All @@ -98,14 +141,13 @@ - (UILabel *)placeholderLabel {
[self updatePlaceholderLabel];
self.needsUpdateFont = NO;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updatePlaceholderLabel)
name:UITextViewTextDidChangeNotification
object:self];

for (NSString *key in self.class.observingKeys) {
[self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
}
__weak typeof(self) weakSelf = self;
[DWTextViewEventObserver observe:self then:^(BOOL fontChanged) {
if (fontChanged) {
weakSelf.needsUpdateFont = fontChanged;
}
[weakSelf updatePlaceholderLabel];
}];
}
return label;
}
Expand Down Expand Up @@ -141,7 +183,6 @@ - (void)setPlaceholderColor:(UIColor *)placeholderColor {
self.placeholderLabel.textColor = placeholderColor;
}


#pragma mark `needsUpdateFont`

- (BOOL)needsUpdateFont {
Expand All @@ -153,21 +194,7 @@ - (void)setNeedsUpdateFont:(BOOL)needsUpdate {
}


#pragma mark - KVO

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"font"]) {
self.needsUpdateFont = (change[NSKeyValueChangeNewKey] != nil);
}
[self updatePlaceholderLabel];
}


#pragma mark - Update

- (void)updatePlaceholderLabel {
if (self.text.length) {
[self.placeholderLabel removeFromSuperview];
Expand All @@ -180,6 +207,7 @@ - (void)updatePlaceholderLabel {
self.placeholderLabel.font = self.font;
self.needsUpdateFont = NO;
}

self.placeholderLabel.textAlignment = self.textAlignment;

// `NSTextContainer` is available since iOS 7
Expand Down
Loading