pbind 0.6.2 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/pbind/command.rb +2 -0
- data/lib/pbind/command/mock.rb +1 -1
- data/lib/pbind/command/serv.rb +78 -16
- data/lib/pbind/command/view.rb +86 -0
- data/lib/pbind/gem_version.rb +1 -1
- data/lib/pbind/minify.rb +52 -0
- data/source/PBLiveLoader/NSInputStream+Reader.m +10 -0
- data/source/PBLiveLoader/PBLLInspector.h +2 -2
- data/source/PBLiveLoader/PBLLInspector.m +385 -22
- data/source/PBLiveLoader/PBLLInspectorController.h +2 -0
- data/source/PBLiveLoader/PBLLInspectorController.m +71 -17
- data/source/PBLiveLoader/PBLLInspectorTipsController.h +23 -0
- data/source/PBLiveLoader/PBLLInspectorTipsController.m +140 -0
- data/source/PBLiveLoader/PBLLOptions.h +1 -1
- data/source/PBLiveLoader/PBLLRemoteWatcher.h +6 -0
- data/source/PBLiveLoader/PBLLRemoteWatcher.m +155 -29
- data/source/PBLiveLoader/PBLLResource.h +25 -0
- data/source/PBLiveLoader/PBLLResource.m +208 -0
- data/source/PBLiveLoader/PBLiveLoader.m +44 -8
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5f96f2b098154b4bb59743bed848868acd9d808
|
4
|
+
data.tar.gz: 630da57494ad83f41a9e20ba9f74e487ee456c6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb44831ec0c62561b94149992a5f69bd358a54edd076518afc34e02a5237e06990ba234cf5ef2313fa5473a80f805555fb889977110c25a2b18cb5fe54f825b8
|
7
|
+
data.tar.gz: 1bb0e99fce43907ab382814832ef081251ef2671dbe1eb9e36e0a386b267a058af15e49241f808e0bb830da60c67e2f46d3c520869d3c721a269229cb9efdfe8
|
data/lib/pbind/command.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'colored'
|
2
2
|
require 'claide'
|
3
3
|
|
4
|
+
require 'pbind/minify'
|
4
5
|
require 'pbind/user_interface'
|
5
6
|
require 'pbind/gem_version.rb'
|
6
7
|
|
@@ -10,6 +11,7 @@ module Pbind
|
|
10
11
|
require_relative 'command/watch'
|
11
12
|
require_relative 'command/mock'
|
12
13
|
require_relative 'command/serv'
|
14
|
+
require_relative 'command/view'
|
13
15
|
|
14
16
|
self.abstract_command = true
|
15
17
|
self.command = 'pbind'
|
data/lib/pbind/command/mock.rb
CHANGED
@@ -27,7 +27,7 @@ module Pbind
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def run
|
30
|
-
@action = @action.gsub(/\//, '
|
30
|
+
@action = @action.gsub(/\//, ':')
|
31
31
|
@api_name = 'PBLocalhost'
|
32
32
|
@project_root = File.dirname(@project_path)
|
33
33
|
@api_install_dir = File.absolute_path(File.join(@project_root, @api_name))
|
data/lib/pbind/command/serv.rb
CHANGED
@@ -27,6 +27,7 @@ module Pbind
|
|
27
27
|
@project_root = File.dirname(@project_path)
|
28
28
|
@src_install_dir = File.absolute_path(File.join(@project_root, @src_name))
|
29
29
|
@api_install_dir = File.absolute_path(File.join(@project_root, @api_name))
|
30
|
+
@api_ignores_file = File.absolute_path(File.join(@api_install_dir, 'ignore.h'))
|
30
31
|
@project = Xcodeproj::Project.open(@project_path)
|
31
32
|
@changed = false
|
32
33
|
|
@@ -59,6 +60,7 @@ module Pbind
|
|
59
60
|
server = TCPServer.new 8082 # Server bind to port
|
60
61
|
@server = server
|
61
62
|
@clients = []
|
63
|
+
@client_names = Hash.new
|
62
64
|
|
63
65
|
addr = server.addr
|
64
66
|
addr.shift
|
@@ -79,10 +81,24 @@ module Pbind
|
|
79
81
|
end
|
80
82
|
|
81
83
|
def handle_request(client, req)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
type = req.bytes[0]
|
85
|
+
msg = req[1..-1]
|
86
|
+
|
87
|
+
if type == 0xC0
|
88
|
+
# Connected
|
89
|
+
@client_names[client] = msg
|
90
|
+
print_client_msg(client, "Connected")
|
91
|
+
elsif type == 0xC1
|
92
|
+
# Request API
|
93
|
+
print_client_msg(client, "Request API '#{msg}'")
|
94
|
+
send_json([client], msg)
|
95
|
+
elsif type == 0xC2
|
96
|
+
# Log
|
97
|
+
print_client_msg(client, msg)
|
98
|
+
elsif type == 0xF1
|
99
|
+
print_client_msg(client, "Apply changed '#{msg}'")
|
100
|
+
elsif type == 0xD0
|
101
|
+
print_client_msg(client, "Got response '#{msg}'")
|
86
102
|
end
|
87
103
|
end
|
88
104
|
|
@@ -116,16 +132,38 @@ module Pbind
|
|
116
132
|
@listener.start # not blocking
|
117
133
|
end
|
118
134
|
|
119
|
-
def send_json(clients,
|
135
|
+
def send_json(clients, api)
|
136
|
+
# Check if ignores
|
137
|
+
file = File.open(@api_ignores_file, "r")
|
138
|
+
file.each_line { |line|
|
139
|
+
if line.start_with? '//'
|
140
|
+
next
|
141
|
+
end
|
142
|
+
|
143
|
+
ignore = line.chomp
|
144
|
+
if ignore.include? api
|
145
|
+
print_serv_msg "Ignores API '#{api}'"
|
146
|
+
clients.each { |client|
|
147
|
+
write_byte client, 0xE0
|
148
|
+
}
|
149
|
+
return
|
150
|
+
end
|
151
|
+
}
|
152
|
+
|
153
|
+
# Read content and minify
|
154
|
+
json_file = "#{api}.json"
|
120
155
|
file = File.open(File.join(@api_install_dir, json_file), "r")
|
121
156
|
content = file.read
|
122
157
|
file.close
|
123
158
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
159
|
+
content = Minify.json(content)
|
160
|
+
|
161
|
+
# Send content
|
162
|
+
print_serv_msg("Send API '#{api}'")
|
163
|
+
clients.each { |client|
|
164
|
+
write_byte client, 0xD0
|
165
|
+
write_string client, api
|
166
|
+
write_string client, content
|
129
167
|
}
|
130
168
|
end
|
131
169
|
|
@@ -145,12 +183,12 @@ module Pbind
|
|
145
183
|
if file_name == nil
|
146
184
|
file_name = File.basename(file_path)
|
147
185
|
end
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
186
|
+
|
187
|
+
print_serv_msg("Update file \"#{file_name}\"")
|
188
|
+
clients.each { |client|
|
189
|
+
write_byte(client, 0xF1)
|
190
|
+
write_string(client, file_name)
|
191
|
+
write_string(client, file_content)
|
154
192
|
}
|
155
193
|
}
|
156
194
|
end
|
@@ -172,6 +210,30 @@ module Pbind
|
|
172
210
|
end
|
173
211
|
end
|
174
212
|
|
213
|
+
def print_serv_msg(msg)
|
214
|
+
print_time
|
215
|
+
print "[Pbind] ".yellow
|
216
|
+
puts msg
|
217
|
+
end
|
218
|
+
|
219
|
+
def print_client_msg(client, msg)
|
220
|
+
print_time
|
221
|
+
device = @client_names[client]
|
222
|
+
if (device == nil)
|
223
|
+
device = "unknown"
|
224
|
+
end
|
225
|
+
print "[#{device}] ".green
|
226
|
+
puts msg
|
227
|
+
end
|
228
|
+
|
229
|
+
def print_time
|
230
|
+
t = Time.now
|
231
|
+
print t.strftime("%H:%M:%S")
|
232
|
+
print '.'
|
233
|
+
print '%03d' % ((t.to_f * 1000).to_i % 1000) # ms
|
234
|
+
print ' '
|
235
|
+
end
|
236
|
+
|
175
237
|
end
|
176
238
|
end
|
177
239
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'xcodeproj'
|
2
|
+
require 'pathname'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Pbind
|
6
|
+
class Command
|
7
|
+
class View < Command
|
8
|
+
self.abstract_command = false
|
9
|
+
self.summary = 'Generate the objective-c view code from Pbind layout.'
|
10
|
+
self.description = <<-DESC
|
11
|
+
This will parse the `PLIST` file and translate it to objective-c code.
|
12
|
+
DESC
|
13
|
+
|
14
|
+
self.arguments = [
|
15
|
+
CLAide::Argument.new(%(PLIST), true),
|
16
|
+
]
|
17
|
+
|
18
|
+
def initialize(argv)
|
19
|
+
super
|
20
|
+
@plist = argv.shift_argument
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate!
|
24
|
+
help! 'The plist is required.' unless @plist
|
25
|
+
help! 'The plist is not exists.' unless File.exists?(@plist)
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
parse_plist(@plist)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
#----------------------------------------#
|
36
|
+
|
37
|
+
# !@group Private helpers
|
38
|
+
|
39
|
+
# Parse the plist and generate code
|
40
|
+
#
|
41
|
+
# @return [void]
|
42
|
+
#
|
43
|
+
def parse_plist(plist_path)
|
44
|
+
plist = Xcodeproj::Plist.read_from_path(plist_path)
|
45
|
+
names = []
|
46
|
+
parents = []
|
47
|
+
|
48
|
+
puts '// Create views'
|
49
|
+
plist["views"].each { |name, view|
|
50
|
+
clazz = view['clazz']
|
51
|
+
properties = view['properties']
|
52
|
+
|
53
|
+
puts "#{clazz} *#{name} = [[#{clazz} alloc] init]; {"
|
54
|
+
|
55
|
+
properties.each { |key, value|
|
56
|
+
puts " #{name}.#{key} = [PBValueParser valueWithString:@\"#{value}\"];"
|
57
|
+
}
|
58
|
+
puts " [self addSubview:#{name}];"
|
59
|
+
|
60
|
+
puts '}'
|
61
|
+
names.push name
|
62
|
+
}
|
63
|
+
|
64
|
+
puts ''
|
65
|
+
puts '// Auto layout'
|
66
|
+
puts 'NSDictionary *views = @{'
|
67
|
+
names.each { |name|
|
68
|
+
puts " @\"#{name}\": #{name},"
|
69
|
+
}
|
70
|
+
puts '};'
|
71
|
+
puts 'NSArray *formats = @['
|
72
|
+
plist["formats"].each { |format|
|
73
|
+
puts " @\"#{format}\","
|
74
|
+
}
|
75
|
+
puts "];"
|
76
|
+
puts <<-CODE
|
77
|
+
for (NSString *format in formats) {
|
78
|
+
NSArray *constraints = [PBLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:metrics views:views];
|
79
|
+
[view addConstraints:constraints];
|
80
|
+
}
|
81
|
+
CODE
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/pbind/gem_version.rb
CHANGED
data/lib/pbind/minify.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Pbind
|
2
|
+
module Minify
|
3
|
+
|
4
|
+
require 'strscan'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def json(str)
|
9
|
+
|
10
|
+
ss = StringScanner.new(str)
|
11
|
+
buf = ''
|
12
|
+
|
13
|
+
until ss.eos?
|
14
|
+
# Remove whitespace
|
15
|
+
if s = ss.scan(/\s+/)
|
16
|
+
|
17
|
+
# Scan punctuation
|
18
|
+
elsif s = ss.scan(/[{},:\]\[]/)
|
19
|
+
buf << s
|
20
|
+
|
21
|
+
# Scan strings
|
22
|
+
elsif s = ss.scan(/""|(".*?[^\\])?"/)
|
23
|
+
buf << s
|
24
|
+
|
25
|
+
# Scan reserved words
|
26
|
+
elsif s = ss.scan(/(true|false|null)/)
|
27
|
+
buf << s
|
28
|
+
|
29
|
+
# Scan numbers
|
30
|
+
elsif s = ss.scan(/-?\d+([.]\d+)?([eE][-+]?[0-9]+)?/)
|
31
|
+
buf << s
|
32
|
+
|
33
|
+
# Remove C++ style comments
|
34
|
+
elsif s = ss.scan(%r{//})
|
35
|
+
ss.scan_until(/(\r?\n|$)/)
|
36
|
+
|
37
|
+
# Remove C style comments
|
38
|
+
elsif s = ss.scan(%r{/[*]})
|
39
|
+
ss.scan_until(%r{[*]/}) || raise(SyntaxError, "Unterminated /*...*/ comment - #{ss.rest}")
|
40
|
+
|
41
|
+
# Anything else is invalid JSON
|
42
|
+
else
|
43
|
+
raise SyntaxError, "Unable to pre-scan string: #{ss.rest}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
buf
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -21,8 +21,18 @@
|
|
21
21
|
|
22
22
|
- (NSData *)readData {
|
23
23
|
int length = [self readInt];
|
24
|
+
if (length < 0) {
|
25
|
+
return nil;
|
26
|
+
}
|
24
27
|
uint8_t *bytes = malloc(length);
|
25
28
|
NSInteger len = [self read:bytes maxLength:length];
|
29
|
+
while (len < length) {
|
30
|
+
len += [self read:bytes + len maxLength:length - len];
|
31
|
+
}
|
32
|
+
if (len != length) {
|
33
|
+
NSLog(@"Failed to read data in length %i, only got %i bytes", length, (int)len);
|
34
|
+
return nil;
|
35
|
+
}
|
26
36
|
return [NSData dataWithBytes:bytes length:len];
|
27
37
|
}
|
28
38
|
|
@@ -9,7 +9,7 @@
|
|
9
9
|
#import "PBLLOptions.h"
|
10
10
|
#include <targetconditionals.h>
|
11
11
|
|
12
|
-
#if (PBLIVE_ENABLED
|
12
|
+
#if (PBLIVE_ENABLED)
|
13
13
|
|
14
14
|
#import <UIKit/UIKit.h>
|
15
15
|
|
@@ -17,7 +17,7 @@
|
|
17
17
|
|
18
18
|
+ (instancetype)sharedInspector;
|
19
19
|
|
20
|
-
|
20
|
+
- (void)updateConnectState:(BOOL)connected;
|
21
21
|
|
22
22
|
@end
|
23
23
|
|
@@ -7,44 +7,407 @@
|
|
7
7
|
|
8
8
|
#import "PBLLInspector.h"
|
9
9
|
|
10
|
-
#if (PBLIVE_ENABLED
|
10
|
+
#if (PBLIVE_ENABLED)
|
11
11
|
|
12
12
|
#import "PBLLInspectorController.h"
|
13
|
+
#import "PBLLInspectorTipsController.h"
|
14
|
+
#import "PBLLResource.h"
|
13
15
|
#import <Pbind/Pbind.h>
|
16
|
+
#import <objc/runtime.h>
|
14
17
|
|
15
|
-
@interface
|
18
|
+
@interface PBLLTipsTapper : UITapGestureRecognizer
|
19
|
+
|
20
|
+
@property (nonatomic, strong) NSString *tips;
|
21
|
+
|
22
|
+
@property (nonatomic, weak) UIView *owner;
|
23
|
+
|
24
|
+
@property (nonatomic, copy) NSString *keyPath;
|
25
|
+
|
26
|
+
@property (nonatomic, assign) BOOL ownerUserInteractionEnabled;
|
27
|
+
|
28
|
+
@end
|
29
|
+
|
30
|
+
@implementation PBLLTipsTapper
|
31
|
+
|
32
|
+
- (NSString *)tips {
|
33
|
+
if (_tips != nil) {
|
34
|
+
return _tips;
|
35
|
+
}
|
36
|
+
|
37
|
+
NSMutableString *s = [NSMutableString string];
|
38
|
+
[s appendString:[_owner valueForAdditionKey:[NSString stringWithFormat:@"~%@", _keyPath]]];
|
39
|
+
if ([_owner isKindOfClass:[UIImageView class]]) {
|
40
|
+
UIImage *image = [(UIImageView *)_owner image];
|
41
|
+
[s appendFormat:@"\nsize: %.1f x %.1f @%.1fx", image.size.width, image.size.height, image.scale];
|
42
|
+
}
|
43
|
+
return s;
|
44
|
+
}
|
45
|
+
|
46
|
+
@end
|
47
|
+
|
48
|
+
@interface PBLLInspector () <UIPopoverPresentationControllerDelegate>
|
49
|
+
{
|
50
|
+
NSString *_identifier;
|
51
|
+
}
|
52
|
+
|
53
|
+
+ (void)showInspectorForView:(UIView *)view;
|
54
|
+
|
55
|
+
@end
|
56
|
+
|
57
|
+
@implementation UIViewController (PBLLInspector)
|
58
|
+
|
59
|
+
static inline void pbll_swizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector) {
|
60
|
+
Method originalMethod = class_getInstanceMethod(class, originalSelector);
|
61
|
+
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
|
62
|
+
if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
|
63
|
+
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
|
64
|
+
} else {
|
65
|
+
method_exchangeImplementations(originalMethod, swizzledMethod);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
+ (void)load {
|
70
|
+
static dispatch_once_t onceToken;
|
71
|
+
dispatch_once(&onceToken, ^{
|
72
|
+
pbll_swizzleSelector([UIViewController class], @selector(viewDidAppear:), @selector(pbll_viewDidAppear:));
|
73
|
+
});
|
74
|
+
}
|
75
|
+
|
76
|
+
- (void)pbll_viewDidAppear:(BOOL)animated {
|
77
|
+
[self pbll_viewDidAppear:animated];
|
78
|
+
|
79
|
+
if ([self isKindOfClass:[UINavigationController class]] || [self isKindOfClass:[PBLLInspectorController class]]) {
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
|
83
|
+
UIView *plistView = [self pbll_findPlistView:self.view];
|
84
|
+
if (plistView == nil) {
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
[PBLLInspector showInspectorForView:plistView];
|
88
|
+
}
|
89
|
+
|
90
|
+
- (UIView *)pbll_findPlistView:(UIView *)view {
|
91
|
+
if (view.plist != nil) {
|
92
|
+
return view;
|
93
|
+
}
|
94
|
+
|
95
|
+
UIView *plistView = nil;
|
96
|
+
for (UIView *subview in view.subviews) {
|
97
|
+
plistView = [self pbll_findPlistView:subview];
|
98
|
+
if (plistView != nil) {
|
99
|
+
break;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
return plistView;
|
103
|
+
}
|
16
104
|
|
17
105
|
@end
|
18
106
|
|
19
107
|
@implementation PBLLInspector
|
20
108
|
|
109
|
+
static const CGFloat kWidth = 44.f;
|
110
|
+
static const CGFloat kHeight = 44.f;
|
111
|
+
|
112
|
+
static BOOL kDisplaysExpression = NO;
|
113
|
+
|
114
|
+
+ (void)load {
|
115
|
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pbactionWillDispatch:) name:PBActionStoreWillDispatchActionNotification object:nil];
|
116
|
+
[Pbind registerViewValueSetter:^id(UIView *view, NSString *keyPath, id value, BOOL *canceld, UIView *contextView, NSString *contextKeyPath) {
|
117
|
+
if (!kDisplaysExpression) {
|
118
|
+
return value;
|
119
|
+
}
|
120
|
+
|
121
|
+
PBExpression *exp = [[contextView pb_expressions] objectForKey:contextKeyPath];
|
122
|
+
if (exp == nil) {
|
123
|
+
return value;
|
124
|
+
}
|
125
|
+
|
126
|
+
if ([keyPath isEqualToString:@"text"]) {
|
127
|
+
return [exp stringValue];
|
128
|
+
}
|
129
|
+
|
130
|
+
return value;
|
131
|
+
}];
|
132
|
+
|
133
|
+
[Pbind registerViewValueAsyncSetter:^(UIView *view, NSString *keyPath, id value, CGSize viewSize, UIView *contextView, NSString *contextKeyPath) {
|
134
|
+
PBExpression *exp = [[contextView pb_expressions] objectForKey:contextKeyPath];
|
135
|
+
if (exp == nil) {
|
136
|
+
return;
|
137
|
+
}
|
138
|
+
|
139
|
+
if ([view isKindOfClass:[UIImageView class]] && [keyPath isEqualToString:@"image"]) {
|
140
|
+
CALayer *imageBgInspectorLayer = [view valueForAdditionKey:@"pb_bg_inspector"];
|
141
|
+
CATextLayer *textInspectorLayer = [view valueForAdditionKey:@"pb_text_inspector"];
|
142
|
+
PBLLTipsTapper *tapper = [view valueForAdditionKey:@"pb_tap_inspector"];
|
143
|
+
if (kDisplaysExpression) {
|
144
|
+
CGRect bounds = (CGRect){.origin = CGPointZero, .size = viewSize};
|
145
|
+
if (imageBgInspectorLayer == nil) {
|
146
|
+
imageBgInspectorLayer = [[CALayer alloc] init];
|
147
|
+
imageBgInspectorLayer.backgroundColor = [[PBLLResource pbindColor] colorWithAlphaComponent:.5f].CGColor;
|
148
|
+
imageBgInspectorLayer.bounds = bounds;
|
149
|
+
imageBgInspectorLayer.position = CGPointZero;
|
150
|
+
imageBgInspectorLayer.anchorPoint = CGPointZero;
|
151
|
+
[view.layer addSublayer:imageBgInspectorLayer];
|
152
|
+
[view setValue:imageBgInspectorLayer forAdditionKey:@"pb_bg_inspector"];
|
153
|
+
}
|
154
|
+
|
155
|
+
if (textInspectorLayer == nil) {
|
156
|
+
textInspectorLayer = [[CATextLayer alloc] init];
|
157
|
+
textInspectorLayer.bounds = CGRectMake(0, 0, viewSize.width, 36);
|
158
|
+
textInspectorLayer.position = CGPointMake(0, viewSize.height / 2);
|
159
|
+
textInspectorLayer.anchorPoint = CGPointMake(0, 0.5);
|
160
|
+
textInspectorLayer.foregroundColor = [UIColor whiteColor].CGColor;
|
161
|
+
textInspectorLayer.alignmentMode = kCAAlignmentCenter;
|
162
|
+
textInspectorLayer.contentsScale = [UIScreen mainScreen].scale;
|
163
|
+
textInspectorLayer.fontSize = 14.f;
|
164
|
+
[view.layer addSublayer:textInspectorLayer];
|
165
|
+
[view setValue:textInspectorLayer forAdditionKey:@"pb_text_inspector"];
|
166
|
+
}
|
167
|
+
textInspectorLayer.string = [NSString stringWithFormat:@"%@\n%.1f x %.1f", exp.stringValue, viewSize.width, viewSize.height];
|
168
|
+
|
169
|
+
if (tapper == nil) {
|
170
|
+
tapper = [[PBLLTipsTapper alloc] initWithTarget:self action:@selector(handleShowTips:)];
|
171
|
+
tapper.owner = view;
|
172
|
+
tapper.keyPath = keyPath;
|
173
|
+
[view addGestureRecognizer:tapper];
|
174
|
+
[view setValue:tapper forAdditionKey:@"pb_tap_inspector"];
|
175
|
+
|
176
|
+
tapper.ownerUserInteractionEnabled = view.userInteractionEnabled;
|
177
|
+
view.userInteractionEnabled = YES;
|
178
|
+
}
|
179
|
+
} else {
|
180
|
+
if (imageBgInspectorLayer != nil) {
|
181
|
+
[imageBgInspectorLayer removeFromSuperlayer];
|
182
|
+
[view setValue:nil forAdditionKey:@"pb_bg_inspector"];
|
183
|
+
}
|
184
|
+
if (textInspectorLayer != nil) {
|
185
|
+
[textInspectorLayer removeFromSuperlayer];
|
186
|
+
[view setValue:nil forAdditionKey:@"pb_text_inspector"];
|
187
|
+
}
|
188
|
+
if (tapper != nil) {
|
189
|
+
view.userInteractionEnabled = tapper.ownerUserInteractionEnabled;
|
190
|
+
[view removeGestureRecognizer:tapper];
|
191
|
+
[view setValue:nil forAdditionKey:@"pb_tap_inspector"];
|
192
|
+
}
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
return;
|
197
|
+
}];
|
198
|
+
}
|
199
|
+
|
21
200
|
+ (instancetype)sharedInspector {
|
22
|
-
static PBLLInspector *
|
201
|
+
static PBLLInspector *inspector;
|
23
202
|
static dispatch_once_t onceToken;
|
24
203
|
dispatch_once(&onceToken, ^{
|
25
|
-
|
204
|
+
inspector = [[PBLLInspector alloc] initWithFrame:CGRectMake(0, 0, kWidth, kHeight)];
|
26
205
|
});
|
27
|
-
return
|
206
|
+
return inspector;
|
28
207
|
}
|
29
208
|
|
30
|
-
+ (void)
|
209
|
+
+ (void)addToController:(UIViewController *)controller withIdentifier:(NSString *)identifier {
|
210
|
+
if (controller == nil) return;
|
211
|
+
|
31
212
|
PBLLInspector *inspector = [self sharedInspector];
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
[
|
41
|
-
[
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
-
|
46
|
-
|
47
|
-
|
213
|
+
if ([inspector.superview isEqual:controller.view]) {
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
|
217
|
+
[inspector removeFromSuperview];
|
218
|
+
[controller.view addSubview:inspector];
|
219
|
+
|
220
|
+
CGPoint center = CGPointZero;
|
221
|
+
CGFloat x = [[NSUserDefaults standardUserDefaults] floatForKey:[NSString stringWithFormat:@"pbind.inspector.x@%@", identifier]];
|
222
|
+
CGFloat y = [[NSUserDefaults standardUserDefaults] floatForKey:[NSString stringWithFormat:@"pbind.inspector.y@%@", identifier]];
|
223
|
+
CGFloat minX = kWidth / 2;
|
224
|
+
CGFloat minY = kHeight / 2;
|
225
|
+
CGFloat maxX = controller.view.frame.size.width - minX;
|
226
|
+
CGFloat maxY = controller.view.frame.size.height - minY;
|
227
|
+
if (x >= minX && x <= maxX) {
|
228
|
+
center.x = x;
|
229
|
+
} else {
|
230
|
+
center.x = maxX - 6.f;
|
231
|
+
}
|
232
|
+
if (y >= minY && y <= maxY) {
|
233
|
+
center.y = y;
|
234
|
+
} else {
|
235
|
+
center.y = maxY - 70.f;
|
236
|
+
}
|
237
|
+
inspector.center = center;
|
238
|
+
inspector->_identifier = identifier;
|
239
|
+
}
|
240
|
+
|
241
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
242
|
+
if (self = [super initWithFrame:frame]) {
|
243
|
+
self.backgroundColor = [UIColor lightGrayColor];
|
244
|
+
self.layer.cornerRadius = 5.f;
|
245
|
+
[self setImage:[PBLLResource logoImage] forState:UIControlStateNormal];
|
246
|
+
// [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
247
|
+
// [self setTitle:@"Pbind" forState:UIControlStateNormal];
|
248
|
+
[self addTarget:self action:@selector(handleClick:) forControlEvents:UIControlEventTouchUpInside];
|
249
|
+
|
250
|
+
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
|
251
|
+
[self addGestureRecognizer:pan];
|
252
|
+
|
253
|
+
#if !(TARGET_IPHONE_SIMULATOR)
|
254
|
+
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
|
255
|
+
[self addGestureRecognizer:longPress];
|
256
|
+
#endif
|
257
|
+
}
|
258
|
+
return self;
|
259
|
+
}
|
260
|
+
|
261
|
+
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
262
|
+
if (newWindow == nil) {
|
263
|
+
UIViewController *vc = [self supercontroller];
|
264
|
+
if (vc.navigationController.topViewController != vc) {
|
265
|
+
// pop
|
266
|
+
kDisplaysExpression = NO;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
#pragma mark - User Interaction
|
272
|
+
|
273
|
+
- (void)handleClick:(id)sender {
|
274
|
+
kDisplaysExpression = !kDisplaysExpression;
|
275
|
+
if (!kDisplaysExpression) {
|
276
|
+
UIViewController *curVC = [self supercontroller];
|
277
|
+
if (curVC.presentedViewController != nil) {
|
278
|
+
[curVC.presentedViewController dismissViewControllerAnimated:YES completion:nil];
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
[[self superview] pb_reloadPlist];
|
283
|
+
}
|
284
|
+
|
285
|
+
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
|
286
|
+
CGPoint translation = [recognizer translationInView:self];
|
287
|
+
CGPoint center = self.center;
|
288
|
+
center.x += translation.x;
|
289
|
+
center.y += translation.y;
|
290
|
+
self.center = center;
|
291
|
+
[recognizer setTranslation:CGPointZero inView:self];
|
292
|
+
|
293
|
+
[[NSUserDefaults standardUserDefaults] setFloat:center.x forKey:[NSString stringWithFormat:@"pbind.inspector.x@%@", _identifier]];
|
294
|
+
[[NSUserDefaults standardUserDefaults] setFloat:center.y forKey:[NSString stringWithFormat:@"pbind.inspector.y@%@", _identifier]];
|
295
|
+
}
|
296
|
+
|
297
|
+
#if !(TARGET_IPHONE_SIMULATOR)
|
298
|
+
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
|
299
|
+
if (sender.state != UIGestureRecognizerStateBegan) {
|
300
|
+
return;
|
301
|
+
}
|
302
|
+
|
303
|
+
UIViewController *currentController = [self supercontroller];
|
304
|
+
if (currentController == nil) {
|
305
|
+
return;
|
306
|
+
}
|
307
|
+
|
308
|
+
[PBLLInspectorController presentFromViewController:currentController];
|
309
|
+
}
|
310
|
+
#endif
|
311
|
+
|
312
|
+
+ (void)handleShowTips:(PBLLTipsTapper *)sender {
|
313
|
+
if (sender.tips == nil) {
|
314
|
+
return;
|
315
|
+
}
|
316
|
+
|
317
|
+
[[self sharedInspector] showTips:sender.tips code:nil onView:sender.owner];
|
318
|
+
}
|
319
|
+
|
320
|
+
#pragma mark - Notification
|
321
|
+
|
322
|
+
- (void)updateConnectState:(BOOL)connected {
|
323
|
+
self.backgroundColor = connected ? [PBLLResource pbindColor] : [UIColor lightGrayColor];
|
324
|
+
}
|
325
|
+
|
326
|
+
+ (void)pbviewDidStartLoad:(NSNotification *)notification {
|
327
|
+
UIView *view = notification.object;
|
328
|
+
[self showInspectorForView:view];
|
329
|
+
}
|
330
|
+
|
331
|
+
+ (void)showInspectorForView:(UIView *)view {
|
332
|
+
NSString *plist = view.plist;
|
333
|
+
if (plist == nil) {
|
334
|
+
return;
|
335
|
+
}
|
336
|
+
|
337
|
+
UIViewController *controller = [view supercontroller];
|
338
|
+
[self addToController:controller withIdentifier:plist];
|
339
|
+
}
|
340
|
+
|
341
|
+
+ (void)pbactionWillDispatch:(NSNotification *)notification {
|
342
|
+
PBAction *action = notification.object;
|
343
|
+
if (action.mapper == nil) {
|
344
|
+
return;
|
345
|
+
}
|
346
|
+
|
347
|
+
if (!kDisplaysExpression) {
|
348
|
+
return;
|
349
|
+
}
|
350
|
+
|
351
|
+
// Prevent default action
|
352
|
+
action.disabled = YES;
|
353
|
+
|
354
|
+
// Build up tips
|
355
|
+
NSMutableDictionary *temp = [NSMutableDictionary dictionary];
|
356
|
+
PBActionState *state = action.store.state;
|
357
|
+
[action.mapper initPropertiesForTarget:temp];
|
358
|
+
[action.mapper mapPropertiesToTarget:temp withData:state.data owner:state.sender context:state.context];
|
359
|
+
NSString *tips = [temp description];
|
360
|
+
|
361
|
+
// Build up code (expression)
|
362
|
+
NSString *code = [[action.mapper targetSource] description];
|
363
|
+
|
364
|
+
[[self sharedInspector] showTips:tips code:code onView:state.sender];
|
365
|
+
}
|
366
|
+
|
367
|
+
#pragma mark - Tips
|
368
|
+
|
369
|
+
- (void)showTips:(NSString *)tips code:(NSString *)code onView:(UIView *)view {
|
370
|
+
UIViewController *curVC = [self supercontroller];
|
371
|
+
if (curVC == nil) {
|
372
|
+
return;
|
373
|
+
}
|
374
|
+
|
375
|
+
PBLLInspectorTipsController *popVC = (id) curVC.presentedViewController;
|
376
|
+
UIPopoverPresentationController *popContainerVC = popVC.popoverPresentationController;
|
377
|
+
if (popContainerVC != nil) {
|
378
|
+
BOOL changed = popContainerVC.sourceView != view || ![tips isEqualToString:popVC.tips];
|
379
|
+
if (changed) {
|
380
|
+
[popVC dismissViewControllerAnimated:NO completion:^{
|
381
|
+
[self showTips:tips code:code onView:view];
|
382
|
+
}];
|
383
|
+
} else {
|
384
|
+
[popVC dismissViewControllerAnimated:YES completion:nil];
|
385
|
+
}
|
386
|
+
} else {
|
387
|
+
popVC = [[PBLLInspectorTipsController alloc] init];
|
388
|
+
popVC.modalPresentationStyle = UIModalPresentationPopover;
|
389
|
+
|
390
|
+
popContainerVC = popVC.popoverPresentationController;
|
391
|
+
popContainerVC.permittedArrowDirections = UIPopoverArrowDirectionAny;
|
392
|
+
popContainerVC.delegate = self;
|
393
|
+
popContainerVC.backgroundColor = [PBLLResource pbindColor];
|
394
|
+
popContainerVC.passthroughViews = @[curVC.view];
|
395
|
+
|
396
|
+
popVC.tips = tips;
|
397
|
+
popVC.code = code;
|
398
|
+
popContainerVC.sourceView = view;
|
399
|
+
popContainerVC.sourceRect = view.bounds;
|
400
|
+
|
401
|
+
[curVC presentViewController:popVC animated:YES completion:nil];
|
402
|
+
}
|
403
|
+
}
|
404
|
+
|
405
|
+
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller{
|
406
|
+
return UIModalPresentationNone;
|
407
|
+
}
|
408
|
+
|
409
|
+
- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController{
|
410
|
+
return YES;
|
48
411
|
}
|
49
412
|
|
50
413
|
@end
|