Compare commits

..

184 Commits

Author SHA1 Message Date
Tanner Bennett e89fec4b2d Bump version 2020-12-17 01:57:25 -06:00
Tanner Bennett 715bb92929 Add confirm dialog for clearing the keychain 2020-12-17 01:35:03 -06:00
Tanner Bennett 109074f98e Show service AND account in keychain rows 2020-12-17 01:34:44 -06:00
Tanner Bennett 45fbdb7914 Move keychain buttons to toolbar 2020-12-17 01:34:33 -06:00
Tanner Bennett cb2e0789d8 Fix several memory leaks, fix #483
Also move a bunch of globals related to FLEXShortcutsFactory into ivars of a FLEXShortcutsFactory singleton so that it is easier to trace the origin of the global storage of FLEX[Property|Ivar|Method] objects in a memory graph
2020-12-17 01:17:40 -06:00
vvveiii de1ca783b6 Fix flex_copy: crash when object is nil 2020-12-16 22:30:11 -08:00
vvveiii 3a9c24b784 Add missing else for flex_copy method 2020-12-16 22:30:11 -08:00
Tanner Bennett b57a333fc9 Fix #491, close #473
Remove (most) uses of +[NSString stringWithCString:encoding:]
2020-12-17 00:20:17 -06:00
matrush 288bf1343e Add ability to toggle bg color for image previews
Also fix availability warning
2020-12-15 16:35:27 -08:00
matrush a0b1caed54 Use UITableViewCellAccessoryNone instead of 0 in FLEX[Layer/View]Shortcuts 2020-12-14 18:38:04 -08:00
Tanner Bennett 9282c61183 Allow scrubbing between selected views
Instead of a singular swipe gesture to navigate between views in the view hierarchy at the tap point, we can now pan or drag along that same area to "srub" the hierarchy.
2020-12-14 18:36:44 -06:00
Tanner Bennett ee6677ee08 Add GUI shortcut for initializeWebKitLegacy
We call initializeWebKitLegacy automatically before you search all bundles just to be safe (since touching some classes before WebKit is initialized will initialize it on a thread other than the main thread), but sometimes you can encounter this crash without searching through all bundles, of course.

In this case, you can now long press on the navigation bar to call initializeWebKitLegacy
2020-12-14 17:10:48 -06:00
Tanner Bennett 3276eb3516 Automatically activate search bar in heap explorer
2f952c38 apparently didn't do this, oops
2020-12-14 17:10:48 -06:00
Chaoshuai Lü a3fa7bbadc Fix view/controller preview image issue (#493) 2020-12-14 16:08:58 -06:00
matrush 637074b354 Use id<NSCopying> as key instead of Class<NSCopying> in FLEXObjectExplorerFactory 2020-12-01 16:56:08 -08:00
Tanner Bennett 547bfbaec0 Disallow preview of views with no rect 2020-11-02 20:42:10 -06:00
Tanner Bennett 4e1fcf4682 Bump version, 4.2.2 2020-11-02 19:36:15 -06:00
Tanner Bennett 4d50fd2020 Revert "Use NSString as key …" in d7786449
We NEED to use the class as a key, because you cannot differentiate a class's name from the metaclass's name.
2020-11-02 19:36:15 -06:00
Tanner Bennett 2c510c8ca1 Fix log timer in example app not firing 2020-11-02 17:45:21 -06:00
Tanner Bennett 8283e2a8e7 Fix #415, restore ability to copy log messages 2020-11-02 17:32:29 -06:00
Tanner Bennett 3f82631a95 Fix bug introduced in 4.2.0, unable to edit members
Oopsie
2020-11-02 17:03:37 -06:00
matrush d77864494d Use NSString as key instead of Class<NSCopying> in FLEXObjectExplorerFactory 2020-11-02 16:58:59 -06:00
matrush c5ed6d4ece Add void to function definition of FLEXConstructorsShouldRun 2020-11-02 16:58:59 -06:00
matrush 9412f6eccf Add a clear global entries method for FLEXManager 2020-11-02 16:58:59 -06:00
Tanner Bennett d9aa102e77 Bump version 2020-10-23 16:42:44 -05:00
Oleg 6f80476135 Fix crash on flex_withTintColor (#476) 2020-10-23 16:23:39 -05:00
Tanner Bennett e18a854a9f Bump version, close #465 #466 2020-10-22 18:18:26 -05:00
Tanner Bennett 0d676e2504 Add FLEX_DISABLE_CTORS, close #471 2020-10-22 18:16:18 -05:00
Tanner Bennett fcca09e74a Fix #428, not scrolling to selected view 2020-10-22 17:57:25 -05:00
Tanner Bennett 107e44a399 Fix #410, prefix all categories 2020-10-22 17:57:21 -05:00
Tanner Bennett e79a3db255 Fix #426
Move explicit FLEXCollection protocol conformances to the .m file to prevent protocol methods from overriding the methods in their headers
2020-10-21 22:55:32 -05:00
Tanner Bennett bec9ffd981 Allow inspecting non-NSObject classes 2020-10-21 21:02:49 -05:00
Tanner Bennett d4070ed9b7 Reverse view snapshot slider ends, fix #429 2020-10-21 21:02:49 -05:00
Tanner Bennett 01e4af47e2 Fix #433, refresh explorer after edits 2020-10-21 21:02:49 -05:00
Tanner Bennett 220af5c350 Table sections can update themselves 2020-10-21 21:02:49 -05:00
Tanner Bennett 15fd16d395 FLEXample project Xcode 12 update
Fix warning in FLEXample
2020-10-21 21:02:49 -05:00
Tanner Bennett af87ea14e0 Update copyright 2020-10-20 18:18:47 -05:00
Tanner Bennett 5c8b334a84 Fix #450: broken search on NSUserDefaults 2020-10-18 17:44:04 -05:00
Tanner Bennett d3c3c61ab1 FLEXMethod.imagePath should use the method's IMP
We can already tell which image the class comes from, and that is usually the same as the original method implementation. What is more useful to know is the image of the _current_ implementation.
2020-10-18 17:17:06 -05:00
Tanner Bennett c28952f6e9 Silence new weird documentation warning… 2020-10-18 17:17:06 -05:00
Tanner Bennett aaf7ffec85 Commit FLEXTests scheme 2020-10-18 17:17:06 -05:00
Tanner Bennett 3ef55eac62 Xcode 12 upgrade check for main scheme 2020-10-18 17:17:06 -05:00
Tanner Bennett 515806e194 Add FLEX_EXIT_IF_NO_CTORS, fix #470 2020-10-18 17:17:06 -05:00
Justin Lam d0d1632ca1 Add foundation header to ActivityStreamSPI. (#461)
* Add C++ ifdefs around C++ code.

The purpose of this change is to gate C++ code behind a cplusplus define so that FLEX can be made into a module for Swift code.  Swift modules do not allow for C++ code.  However this means if FLEX is used a Swift module, it will not have functionality of the excluded code. Ideally in the future, FLEX can be converted fully to support Swift without bridging headers.

* Revert "Add C++ ifdefs around C++ code."

This reverts commit 77c02207f9.

* Add include <Foundation/Foundation.h> to ActivityStreamSPI header

We use a different build setup internally, and we need all headers to be able to stand on their own. This file is using NS_ENUM, which is defined in Foundation, without importing it, so we're adding the import to resolve that.
2020-10-13 14:31:31 -05:00
matrush 79e22cf828 Remove a retain cylce in FLEXSystemLogViewController 2020-10-13 14:31:31 -05:00
Iulian Onofrei 563cb514a1 Fix crash when opening Keychain item without a password 2020-10-13 14:31:31 -05:00
matrush 84131d8533 Return empty array when the rows are nil in FLEXSQLResult 2020-10-13 14:31:31 -05:00
Evan Emelga 55cb7ebf8f Sort realm table names alphabetically (#453) 2020-10-12 23:07:18 -05:00
Anıl Taşkıran b9e2c1ebd9 fix baseResumeClass name for iOS14+ 2020-10-12 23:07:18 -05:00
Maxime Ollivier 7377399673 Skip keyboard shortcut override when setting defaults 2020-10-12 23:07:18 -05:00
Tanner Bennett 21199b2a56 Add sharedApplication property at runtime for iOS 9 2020-10-12 23:07:18 -05:00
matrush e72c6aa349 Add better debugging message for FLEXProperty and FLEXIvar 2020-10-12 23:06:55 -05:00
ph661 13c583d32b Add SceneKit to podspec frameworks 2020-10-12 19:19:34 -05:00
Chaoshuai Lü 129c291469 Move custom additions to the top (#439)
- Move custom additions to the top
- Fix header
2020-10-12 19:19:34 -05:00
Chris Ellsworth 67609b28a4 Add sorting in File Browser 2020-10-12 19:17:58 -05:00
matrush 4dc206eaa6 Add copy action for database browser row selection alert 2020-10-12 19:17:57 -05:00
matrush 28e91507db Remove retain cycles from selectionHandler in several files 2020-07-07 12:05:05 -05:00
matrush 5f74fb0d43 Remove redundant FLEXPointerIsTaggedPointer function 2020-07-07 12:04:34 -05:00
Tanner Bennett 09f5859feb Add InAppViewDebugger thanks to README 2020-06-30 16:25:12 -05:00
Hao Nguyen cee416889a use __typeof instead of typeof to compile for Cxx instead of GNUxx 2020-06-19 01:39:48 -05:00
Tanner Bennett e2a334384a Misc cleanup and bug fixes 2020-05-25 17:23:18 -05:00
Tanner Bennett a840e909a1 Add UIApplication shortcuts 2020-05-25 17:12:46 -05:00
Tanner Bennett 2f952c380f Automatically activate search bar in heap explorer 2020-04-27 17:53:34 -05:00
Tanner Bennett 5d919eb329 Don't access ivars on tagged pointers 2020-04-25 00:51:58 -05:00
Tanner Bennett 1d5d825135 Fix class properties not showing previews 2020-04-25 00:51:36 -05:00
opa334 2a8cdbdb84 Fix heap enumeration on arm64e 2020-04-23 22:04:41 -05:00
Tanner Bennett 5e2081b8f9 Various performance improvements
- Remove ARC from -[FLEXTypeEncodingParser canScanChar:]
- Make FLEXMethod.imagePath lazy
2020-04-23 22:04:41 -05:00
Bas Broek fbaeda1956 Remove parenthesis in Swift (#414) 2020-04-16 10:34:27 -05:00
Tanner Bennett c761865b9b Namespace all fishhook functions, fix #408
I know not all of them needed to be namespaced as the private functions are static, but I did it anyway for consistency.
2020-04-06 17:47:47 -05:00
Tanner Bennett 700c50af5d Bump version 2020-04-06 17:37:59 -05:00
Tanner Bennett b38cca06b1 Fix ProtocolMember instance field
The ProtocolMember instance field was not being populated in runtime exports
2020-04-06 17:32:43 -05:00
Tanner Bennett 6429573918 Rename some classes with excessively long names 2020-04-06 17:32:43 -05:00
Tanner Bennett f77f5ccdc9 Push straight to single instances in heap explorer
Take the user straight to the explorer itself if there is only one instance of the selected class
2020-04-06 17:32:43 -05:00
Tanner Bennett 7aeddcdb2c Add search bar to view controllers list 2020-04-06 17:32:11 -05:00
Tanner Bennett a25ef87a51 Make new JSON viewer and System Log behavior opt-in 2020-04-06 17:32:11 -05:00
Tanner Bennett fbeb1beca0 Namespace some category filenames 2020-04-06 17:32:11 -05:00
Tanner Bennett 059bde9711 Fix crash in flex_all* methods 2020-04-06 17:32:11 -05:00
Tanner Bennett 2ca563f570 Bug fix: iPad support for FLEXAlert action sheets 2020-04-06 17:32:11 -05:00
Tanner Bennett 88c7ca9373 Add option to disable property/ivar previews 2020-03-31 12:16:57 -05:00
Tanner Bennett 83486641aa Add < iOS 13 support to example project 2020-03-30 16:28:19 -05:00
Tanner Bennett 6bd0c87881 Fix import statement to work without modules 2020-03-30 16:28:19 -05:00
Tanner Bennett 1a64da70c9 Add FLEXRuntimeExporter
Allows you to export the contents of a bundle's objc metadata as an SQLite database

fix

fix
2020-03-30 16:28:19 -05:00
Tanner Bennett 87ea2bb147 FLEXRuntimeClient additions
- Move initializeWebKitLegacy to FLEXRuntimeClient
- Add -copySafeClassList and -copyProtocolList
2020-03-27 19:59:32 -05:00
Tanner Bennett d9e9be53d8 Various database browser related upgrades
- All database managers automatically open and close connections to the underlying database
- Allow getting last result and last rowid from FLEXSQLiteDatabaseManager
- Execute statements with arguments with FLEXSQLiteDatabaseManager
2020-03-27 19:59:32 -05:00
Tanner Bennett 142f037497 Runtime wrapper upgrades
- Make sure every object has an `imageName` property
- Expose more fine-grained metadata through FLEXProtocol
- Migrate flex_all* methods to top-level functions that the methods call into
2020-03-27 19:59:32 -05:00
Tanner Bennett 6cdb626d78 Various bug fixes 2020-03-27 19:59:32 -05:00
Tanner Bennett 6e81029b8b Show HTTP status code in commit screen error 2020-03-27 19:16:09 -05:00
Tanner Bennett 1c7048e710 Dispatch JSON viewer registration to main queue 2020-03-25 15:12:34 -05:00
Tanner Bennett 23c7cfbe6e Update README.md 2020-03-24 15:17:59 -05:00
Tanner Bennett b45750eb1b Update .gitignore 2020-03-24 14:53:32 -05:00
Tanner Bennett 550c9c1120 Add new Swift Example project
The project is built with FLEXFilteringTableViewController and utilizes FLEXObjectExplorerFactory to demonstrate how to use them in your own projects. It also shows how to properly import use FLEX in Swift projects.
2020-03-24 14:53:32 -05:00
Tanner Bennett d5dfb23cf2 Fix possible shortcuts bug 2020-03-24 14:53:32 -05:00
Tanner Bennett 90ee4f8f38 Fix behavior of M key shortcut 2020-03-24 14:33:01 -05:00
Tanner Bennett c037e703a6 Fix runtime browser crashing when searching for *.*.
Also add type annotations to several arrays
2020-03-24 14:20:36 -05:00
Tanner Bennett 6bf746d4aa Initialize WebKitLegacy when exploring all classes 2020-03-24 14:20:36 -05:00
Tanner Bennett ca005fb4d0 Make FLEXClassIsSafe return NO for unknown root classes 2020-03-24 14:19:00 -05:00
Tanner Bennett 5f2bac9c2f Fix network search adding all new entries to search results 2020-03-24 14:18:52 -05:00
Tanner Bennett dc0e7dc7d3 Tap navigation bar to reveal toolbar if hidden 2020-03-24 14:18:52 -05:00
Tanner Bennett 740daab55b Add network host blacklisting in UI
Make networkRequestHostBlacklist a mutable array
2020-03-24 14:18:52 -05:00
Tanner Bennett 1953089653 Move "copy curl" button to the toolbar 2020-03-24 11:50:53 -05:00
Tanner Bennett 25e0af042e Fix network screen not updating in the background 2020-03-24 11:50:53 -05:00
Tanner Bennett 0265334976 Truncate object descriptions to 10000 characters
Also fix description sizing bug
2020-03-24 11:50:53 -05:00
Chaoshuai Lü 6aa9ec9ec1 Make some functions static to avoid warnings (#394) 2020-03-24 10:40:36 -05:00
Tanner Bennett 459aa9b6f5 Move FLEXManager+Private.h 2020-03-18 16:30:46 -05:00
Tanner Bennett 06655dde6a More type encoding parser tests + bug fixes 2020-03-18 16:30:46 -05:00
Tanner Bennett 5c153b0c89 Work around AFNetworking breaking network observer 2020-03-18 16:30:46 -05:00
Tanner Bennett 29dd970ea7 Whoops 2020-03-16 17:25:54 -05:00
Tanner Bennett f6701f8ec9 Move network monitor "Clear All" button to the toolbar 2020-03-16 17:25:54 -05:00
Tanner Bennett 7f119ba0cc Add option to reveal overridden methods
- Publish certain preference changes with NSNotificationCenter so as to update all screens observing these changes
- Observe these preferences in appropriate screens
2020-03-16 14:41:46 -05:00
Tanner Bennett e7e5115fc0 Clean up FLEXNetworkObserver.m 2020-03-16 13:15:13 -05:00
Tanner Bennett 0e0bd5a890 Don't add a "Done" button unless we're being presented 2020-03-16 13:15:13 -05:00
Tanner Bennett 9cc2470901 Add some missing nullability specifiers
Improve the Swift experience a little bit
2020-03-16 13:15:13 -05:00
Tanner Bennett b07da3e11d Update copyright dates 2020-03-16 13:15:13 -05:00
Tanner Bennett 36e0e9fb1e Bump version, expose more public headers 2020-03-16 13:15:12 -05:00
Tanner Bennett 0e85162c11 Fix or silence various warnings
warnings
2020-03-16 13:15:12 -05:00
Tanner Bennett 7329fd9272 Expose toolbar as a public property on FLEXManager 2020-03-11 16:57:43 -05:00
Tanner Bennett 30fb8f077a Forgot to format FLEXResources.m 2020-03-10 18:21:14 -05:00
Tanner Bennett f2f66489d1 Use properties where applicable 2020-03-10 18:16:27 -05:00
Tanner Bennett 3cb1366966 Replace +array / +string / +set … calls with +new 2020-03-10 18:16:27 -05:00
Tanner Bennett 907b315601 Namespace files in RuntimeBrowser/ 2020-03-10 18:16:26 -05:00
Tanner Bennett 877a1db87b Add ability to hide property-backing ivars+methods 2020-03-10 16:38:33 -05:00
Tanner Bennett 5b6b50bf6a Fix various gesture bugs in the FLEX toolbar
My bad y'all
2020-03-10 16:38:33 -05:00
Tanner Bennett 6c8fbbeaa8 Fix system log in simulator 2020-03-10 16:38:33 -05:00
Tanner Bennett da67902cf5 Add automatic filtering table view controller
Also add FLEXMutableListSection which wraps the collection content section in a way that makes displaying a simple list of content straightforward.

Adopt additions in appropriate view controllers.
2020-03-10 16:38:33 -05:00
Tanner Bennett 8533689ca7 Clean up FLEXNetworkRecorder
Network history will prompt to enable the monitor on tap
2020-03-10 15:23:44 -05:00
Tanner Bennett 8f85b22866 Recent icon change 2020-03-09 12:20:37 -05:00
Tanner Bennett fa8a4d61ea New icons 2020-03-09 12:20:37 -05:00
Tanner Bennett 89010395de Search upgrades
- Add ability to search object lists (instances, references, subclasses)
- Fix broken search for collection exploreres
2020-03-09 12:20:37 -05:00
Tanner Bennett 5b969b6438 Refactor FLEXClassShortcuts, add "List Subclasses" 2020-03-09 12:20:37 -05:00
Tanner Bennett 2dee6901f7 Refactor NSObject+Reflection 2020-03-09 12:20:37 -05:00
Tanner Bennett fced419509 Bug fixes / code cleanup
Also make image preview use checker background
2020-03-09 12:20:37 -05:00
Tanner Bennett 09ff0482df iOS 9-10 fixes 2020-03-09 12:20:37 -05:00
Tanner Bennett d9986d879b Fix broken os_log_shim_enabled substrate hook
Not sure how I ever thought it was working the way it was before in the first place…
2020-03-09 12:20:37 -05:00
Les Melnychuk 15d7d07809 Add ability to run SQL queries 2020-03-03 12:41:45 -06:00
Tanner Bennett a556ece626 Tidy up database browser 2020-03-02 17:51:08 -06:00
Tanner Bennett 35ce037288 No longer override UIMenuController items 2020-03-02 17:51:08 -06:00
Tanner Bennett a32b4074f8 Fix pasting not working in runtime browser 2020-02-28 16:12:21 -06:00
Tanner Bennett c3da7e10f7 Improve runtime browser accessory view behavior
- Buttons are right-aligned when they don't fill the toolbar
- Keyboard now has period key, giving more space to other buttons in the toolbar
- Suggestions for classes and bundles are now added to the toolbar
- When nothing has been typed, the default suggestion is the bundle for the current app
- Make the colors closer to the real keyboard colors
2020-02-28 16:12:21 -06:00
Tanner Bennett 135c8d05c1 Carousel header works on all supported iOS versions 2020-02-28 16:12:21 -06:00
Tanner Bennett 8afd1a1975 Selecting a Mach-O file will push an array of classes 2020-02-27 11:18:16 -06:00
Tanner Bennett dcdd638719 Don't call methods on objects in instances screen 2020-02-27 11:18:16 -06:00
Tanner Bennett c661d491a5 Use SF Mono / Menlo for log font 2020-02-27 11:18:16 -06:00
Tanner Bennett 82f104d682 Runtime browser improvements 2020-02-24 18:03:07 -06:00
Tanner Bennett f9e42aed74 Support NSDecimalNumber setters 2020-02-24 18:03:07 -06:00
Tanner Bennett c8343500af Tab-related fixes 2020-02-24 18:03:07 -06:00
Tanner Bennett 5a96ed6af5 Add a swipe-to-dismiss gesture for fullscreen pages
Swipe down on the navigation bar to dismiss fullscreen view controllers. Also, every view controller on the navigation stack gets a "done" button.
2020-02-24 18:03:07 -06:00
Tanner Bennett 84cdc6a8e4 Fix priorities of swipe / pan gestures in explorer 2020-02-24 18:03:07 -06:00
Tanner Bennett 2b6ccb23e4 Exclude swipe gestures from the carousel 2020-02-24 18:03:07 -06:00
Tanner Bennett be02b89d9b Add swipe gestures to select a more shallow or deeper view 2020-02-24 18:03:07 -06:00
Tanner Bennett b4f07a0f92 Remove unnecessary refresh control in object explorer
Most update as you scroll, there is little benefit to even having a refresh control. It also inhibits the drag-to-dismiss iOS 13 sheet gesture.
2020-02-24 18:03:07 -06:00
Tanner Bennett 2093913b17 Add missing accessory button to scenes in windows screen 2020-02-24 18:03:07 -06:00
Tanner Bennett 7705dac42b Add shortcuts for various foundation classes 2020-02-24 18:03:07 -06:00
Tanner Bennett d23f01dd87 Move UIView(Controller) runtime property additions
… to FLEXShortcutsFactory+Defaults.m
2020-02-24 18:03:07 -06:00
Tanner Bennett ec2b73ceda Add some objc runtime nullability safeguards 2020-02-24 18:03:07 -06:00
Tanner Bennett b8f226ce45 Add more singletons to the globals screen 2020-02-24 18:03:07 -06:00
Tanner Bennett b232ddb075 Fix shortcut registration logic
Fix shortcuts
2020-02-24 18:03:07 -06:00
Tanner Bennett 87a821903d Misc FLEX*Utility changes
Also don't load NSProxy categories if testing
2020-02-24 18:03:07 -06:00
Tanner Bennett 9645811baa Don't call other methods on target in performSelector: 2020-02-24 18:03:07 -06:00
Tanner Bennett 2e2a550b1f Nullability fixes for runtime functions 2020-02-24 18:03:07 -06:00
Tanner Bennett 98dff514e8 Moooooore type encoding parser fixes
- Case: NSMethodSignature does not support unions
- Case: NSMethodSignature passes incompete pointer types to NSGetSizeAndAlignment which throws an exception
2020-02-24 18:03:07 -06:00
Tanner Bennett 9332a87d98 FLEXTypeEncodingParser: Improve malformed array detection 2020-02-24 18:03:07 -06:00
Tanner Bennett d031dab174 Actually disable os_log for jailbroken devices
os_log cannot be disabled on-device without a trampoline-style function hook. Fishhook just rebinds lazy symbols, which isn't working on-device for some reason. This commit makes use of Substrate's MSHookFunction to do this, if it is available.
2020-02-24 18:03:07 -06:00
Tanner Bennett 7ddb46ff9f Update parser: NSMethodSignature doesn't support unions 2020-02-19 16:10:33 -06:00
Tanner Bennett 4a4a08df16 Optimize FLEXTypeEncodingParser 2020-02-19 16:10:33 -06:00
Tanner Bennett 4830daeab3 Fix exceptions caught by NSMethodSignature
FLEXTypeEncodingParser worked as originally intended, but getting it working helped me realize something else: NSMethodSignature will pass structure member pointer types to NSGetSizeAndAlignment and catch any exceptions it throws.

So now FLEXTypeEncoding must parse and "clean" unsupported pointer types in method signatures to avoid those exceptions, where previously we didn't care about opinter types at all.

+[FLEXTypeEncoding methodTypeEncodingSupported:] becomes +[FLEXTypeEncoding methodTypeEncodingSupported:cleaned:] where `cleaned` is an NSString out param which stores the "cleaned" method signature (or the input signature if nothing needed cleaning). This is then passed to NSMethodSignature.

This commit also fixes a few other bugs, like arithmetic errors on when calculating the size of "v" and union size calculation.
2020-02-19 16:10:33 -06:00
Tanner Bennett dd05a6652c Remove needless refresh on viewWillAppear: 2020-02-19 16:10:33 -06:00
Tanner Bennett e78947260d Refactor FLEXToolbar*, add a "recent" button
Recent button presents most recently active tab, if any
2020-02-17 22:38:18 -06:00
Tanner Bennett 5dd856070e Undo comment in app delegate 2020-02-17 15:26:12 -06:00
Tanner Bennett 2e868eba39 Add more descriptive titles to editor screens 2020-02-13 17:21:20 -06:00
Tanner Bennett 9ba80a53cf Add fishhook, disable OS log — close #372
iOS 10 and its associated SDK deprecated *ASL and replaced it with *os_log. This change is widely considered unfavorable and made it extremely tedious for FLEX to intercept log messages reliably.

@Ram4096 has brought to my attention that the os_log functionality is actually just a shim which is conditionally enabled based on what SDK version your binary links with. With a little reverse engineering, I was able to hook the function that tells `NSLog` (well, `CFLogv`) whether os_log should be used or not. This commit uses fishhook to hook `os_log_shim_enabled` to always return `NO` so that the old ASL library is used instead.

Prior to this commit we had code in place to conditionally intercept messages from os_log or ASL based on the iOS version. These checks are not semantically correct since ASL would still be used on iOS 10+ if the binary was built with the iOS 9 SDK. For now, this doesn't matter going forward since we are going to always use ASL, but it might be worth updating the check to instead check for the linked SDK version instead of the OS version.

- *ASL: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/asl.3.html
- *os_log: https://developer.apple.com/documentation/os/logging?language=objc
2020-02-13 15:46:59 -06:00
Tanner Bennett 1780968f50 Shorten system log class names 2020-02-13 15:31:38 -06:00
Tanner Bennett 9abd9cc933 Reorganize Utility folder 2020-02-13 15:26:36 -06:00
Tanner Bennett 18f20fbf3b Long press on hierarchy item to show VCs at tap 2020-02-13 12:53:45 -06:00
Tanner Bennett 3e6d18dd8c Allow exploring objects from window/scene screen 2020-02-13 12:52:51 -06:00
Tanner Bennett 99d2ddd001 Adopt FLEXTypeEncodingParser
Debugging will be soooo much less frustrating now that we aren't throwing exceptions all over the place.

Also, we no longer load pre-registered shortcuts in testing environments. This may come back to bite me in the butt if it turns out the XCTest class exists in UI tests or something.
2020-02-11 19:08:55 -06:00
Tanner Bennett 1647f7ab9f Add FLEXTypeEncodingParser 2020-02-11 19:05:45 -06:00
Tanner Bennett 5ae64e17e6 Fix crash in network screen while searching 2020-02-11 16:57:38 -06:00
Tanner Bennett c5d4e959cd SwiftObject-related fixes 2020-02-11 16:57:38 -06:00
Tanner Bennett c056a29375 Add window and scene managemenet screen
Also fix potential bugs in FLEXExplorerViewController
2020-02-11 16:57:38 -06:00
Tanner Bennett 02409d8051 Add basic support for bookmarking objects 2020-02-11 16:57:38 -06:00
Tanner Bennett 805434239a Fix bugs in collection and defaults content sections
FLEXCollectionContentSection and FLEXDefaultsContentSection
2020-02-11 16:57:38 -06:00
Tanner Bennett 179c968443 Fix bugs in FLEXNetworkHistoryTableViewController
Also, rename it to FLEXNetworkMITMViewController
2020-02-11 16:57:38 -06:00
Tanner Bennett cfc68017e0 Change OS_ACTIVITY_MODE to OS_ACTIVITY_DT_MODE
Silences annoying internal crap in the console
2020-02-11 16:57:38 -06:00
Tanner Bennett 2300d68321 Add basic support for tabs
Other changes:
- Editor/caller view controllers use a toolbar for the call/set button now
- FLEXNavigationController adds the Done button to it's root view controller instead of FLEXExplorerViewController
- FLEXExplorerViewController now overrides presentViewController: and dismissViewControllerAnimated: to toggle its window's key status instead of using new methods to do it
- Adds a 't' simulator shortcut to quickly present an explorer screen for testing
2020-02-11 16:57:38 -06:00
577 changed files with 16175 additions and 26329 deletions
+3
View File
@@ -17,3 +17,6 @@ DerivedData
*.ipa
*.xcuserstate
.DS_Store
/Example/Pods
Podfile.lock
IDEWorkspaceChecks.plist
@@ -0,0 +1,89 @@
//
// FLEXFilteringTableViewController.h
// FLEX
//
// Created by Tanner on 3/9/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewController.h"
#pragma mark - FLEXTableViewFiltering
@protocol FLEXTableViewFiltering <FLEXSearchResultsUpdating>
/// An array of visible, "filtered" sections. For example,
/// if you have 3 sections in \c allSections and the user searches
/// for something that matches rows in only one section, then
/// this property would only contain that on matching section.
@property (nonatomic, copy) NSArray<FLEXTableViewSection *> *sections;
/// An array of all possible sections. Empty sections are to be removed
/// and the resulting array stored in the \c section property. Setting
/// this property should immediately set \c sections to \c nonemptySections
///
/// Do not manually initialize this property, it will be
/// initialized for you using the result of \c makeSections.
@property (nonatomic, copy) NSArray<FLEXTableViewSection *> *allSections;
/// This computed property should filter \c allSections for assignment to \c sections
@property (nonatomic, readonly, copy) NSArray<FLEXTableViewSection *> *nonemptySections;
/// This should be able to re-initialize \c allSections
- (NSArray<FLEXTableViewSection *> *)makeSections;
@end
#pragma mark - FLEXFilteringTableViewController
/// A table view which implements \c UITableView* methods using arrays of
/// \c FLEXTableViewSection objects provied by a special delegate.
@interface FLEXFilteringTableViewController : FLEXTableViewController <FLEXTableViewFiltering>
/// Stores the current search query.
@property (nonatomic, copy) NSString *filterText;
/// This property is set to \c self by default.
///
/// This property is used to power almost all of the table view's data source
/// and delegate methods automatically, including row and section filtering
/// when the user searches, 3D Touch context menus, row selection, etc.
///
/// Setting this property will also set \c searchDelegate to that object.
@property (nonatomic, weak) id<FLEXTableViewFiltering> filterDelegate;
/// Defaults to \c NO. If enabled, all filtering will be done by calling
/// \c onBackgroundQueue:thenOnMainQueue: with the UI updated on the main queue.
@property (nonatomic) BOOL filterInBackground;
/// Defaults to \c NO. If enabled, one • will be supplied as an index title for each section.
@property (nonatomic) BOOL wantsSectionIndexTitles;
/// Recalculates the non-empty sections and reloads the table view.
///
/// Subclasses may override to perform additional reloading logic,
/// such as calling \c -reloadSections if needed. Be sure to call
/// \c super after any logic that would affect the appearance of
/// the table view, since the table view is reloaded last.
///
/// Called at the end of this class's implementation of \c updateSearchResults:
- (void)reloadData;
/// Invoke this method to call \c -reloadData on each section
/// in \c self.filterDelegate.allSections.
- (void)reloadSections;
#pragma mark FLEXTableViewFiltering
@property (nonatomic, copy) NSArray<FLEXTableViewSection *> *sections;
@property (nonatomic, copy) NSArray<FLEXTableViewSection *> *allSections;
/// Subclasses can override to hide specific sections under certain conditions
/// if using \c self as the \c filterDelegate, as is the default.
///
/// For example, the object explorer hides the description section when searching.
@property (nonatomic, readonly, copy) NSArray<FLEXTableViewSection *> *nonemptySections;
/// If using \c self as the \c filterDelegate, as is the default,
/// subclasses should override to provide the sections for the table view.
- (NSArray<FLEXTableViewSection *> *)makeSections;
@end
@@ -0,0 +1,212 @@
//
// FLEXFilteringTableViewController.m
// FLEX
//
// Created by Tanner on 3/9/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXFilteringTableViewController.h"
#import "FLEXTableViewSection.h"
#import "NSArray+FLEX.h"
@interface FLEXFilteringTableViewController ()
@end
@implementation FLEXFilteringTableViewController
@synthesize allSections = _allSections;
#pragma mark - View controller lifecycle
- (void)loadView {
[super loadView];
if (!self.filterDelegate) {
self.filterDelegate = self;
} else {
[self _registerCellsForReuse];
}
}
- (void)_registerCellsForReuse {
for (FLEXTableViewSection *section in self.filterDelegate.allSections) {
if (section.cellRegistrationMapping) {
[self.tableView registerCells:section.cellRegistrationMapping];
}
}
}
#pragma mark - Public
- (void)setFilterDelegate:(id<FLEXTableViewFiltering>)filterDelegate {
_filterDelegate = filterDelegate;
filterDelegate.allSections = [filterDelegate makeSections];
if (self.isViewLoaded) {
[self _registerCellsForReuse];
}
}
- (void)reloadData {
[self reloadData:self.nonemptySections];
}
- (void)reloadData:(NSArray *)nonemptySections {
// Recalculate displayed sections
self.filterDelegate.sections = nonemptySections;
// Refresh table view
if (self.isViewLoaded) {
[self.tableView reloadData];
}
}
- (void)reloadSections {
for (FLEXTableViewSection *section in self.filterDelegate.allSections) {
[section reloadData];
}
}
#pragma mark - Search
- (void)updateSearchResults:(NSString *)newText {
NSArray *(^filter)() = ^NSArray *{
self.filterText = newText;
// Sections will adjust data based on this property
for (FLEXTableViewSection *section in self.filterDelegate.allSections) {
section.filterText = newText;
}
return nil;
};
if (self.filterInBackground) {
[self onBackgroundQueue:filter thenOnMainQueue:^(NSArray *unused) {
if ([self.searchText isEqualToString:newText]) {
[self reloadData];
}
}];
} else {
filter();
[self reloadData];
}
}
#pragma mark Filtering
- (NSArray<FLEXTableViewSection *> *)nonemptySections {
return [self.filterDelegate.allSections flex_filtered:^BOOL(FLEXTableViewSection *section, NSUInteger idx) {
return section.numberOfRows > 0;
}];
}
- (NSArray<FLEXTableViewSection *> *)makeSections {
return @[];
}
- (void)setAllSections:(NSArray<FLEXTableViewSection *> *)allSections {
_allSections = allSections.copy;
// Only display nonempty sections
self.sections = self.nonemptySections;
}
- (void)setSections:(NSArray<FLEXTableViewSection *> *)sections {
// Allow sections to reload a portion of the table view at will
[sections enumerateObjectsUsingBlock:^(FLEXTableViewSection *s, NSUInteger idx, BOOL *stop) {
[s setTable:self.tableView section:idx];
}];
_sections = sections.copy;
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.filterDelegate.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.filterDelegate.sections[section].numberOfRows;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return self.filterDelegate.sections[section].title;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *reuse = [self.filterDelegate.sections[indexPath.section] reuseIdentifierForRow:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuse forIndexPath:indexPath];
[self.filterDelegate.sections[indexPath.section] configureCell:cell forRow:indexPath.row];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView {
if (self.wantsSectionIndexTitles) {
return [NSArray flex_forEachUpTo:self.filterDelegate.sections.count map:^id(NSUInteger i) {
return @"";
}];
}
return nil;
}
#pragma mark - UITableViewDelegate
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
return [self.filterDelegate.sections[indexPath.section] canSelectRow:indexPath.row];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section];
void (^action)(UIViewController *) = [section didSelectRowAction:indexPath.row];
UIViewController *details = [section viewControllerToPushForRow:indexPath.row];
if (action) {
action(self);
[tableView deselectRowAtIndexPath:indexPath animated:YES];
} else if (details) {
[self.navigationController pushViewController:details animated:YES];
} else {
[NSException raise:NSInternalInconsistencyException
format:@"Row is selectable but has no action or view controller"];
}
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
[self.filterDelegate.sections[indexPath.section] didPressInfoButtonAction:indexPath.row](self);
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section];
NSString *title = [section menuTitleForRow:indexPath.row];
NSArray<UIMenuElement *> *menuItems = [section menuItemsForRow:indexPath.row sender:self];
if (menuItems.count) {
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
return [UIMenu menuWithTitle:title children:menuItems];
}
];
}
return nil;
}
#endif
@end
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 1/30/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 1/30/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXNavigationController.h"
@@ -20,35 +20,54 @@
@interface FLEXNavigationController ()
@property (nonatomic, readonly) BOOL toolbarWasHidden;
@property (nonatomic) BOOL waitingToAddTab;
@property (nonatomic) BOOL didSetupPendingDismissButtons;
@property (nonatomic) UISwipeGestureRecognizer *navigationBarSwipeGesture;
@end
@implementation FLEXNavigationController
+ (instancetype)withRootViewController:(UIViewController *)rootVC {
FLEXNavigationController *instance = [[self alloc] initWithRootViewController:rootVC];
// Give root view controllers a Done button
UIBarButtonItem *done = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:instance
action:@selector(dismissAnimated)
];
// Prepend the button if other buttons exist already
NSArray *existingItems = rootVC.navigationItem.rightBarButtonItems;
if (existingItems.count) {
rootVC.navigationItem.rightBarButtonItems = [@[done] arrayByAddingObjectsFromArray:existingItems];
} else {
rootVC.navigationItem.rightBarButtonItem = done;
}
return instance;
return [[self alloc] initWithRootViewController:rootVC];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.waitingToAddTab = YES;
// Add gesture to reveal toolbar if hidden
self.navigationBar.userInteractionEnabled = YES;
[self.navigationBar addGestureRecognizer:[[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(handleNavigationBarTap:)
]];
// Add gesture to dismiss if not presented with a sheet style
if (@available(iOS 13, *)) {
switch (self.modalPresentationStyle) {
case UIModalPresentationAutomatic:
case UIModalPresentationPageSheet:
case UIModalPresentationFormSheet:
break;
default:
[self addNavigationBarSwipeGesture];
break;
}
} else {
[self addNavigationBarSwipeGesture];
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.beingPresented && !self.didSetupPendingDismissButtons) {
for (UIViewController *vc in self.viewControllers) {
[self addNavigationBarItemsToViewController:vc.navigationItem];
}
self.didSetupPendingDismissButtons = YES;
}
}
- (void)viewDidAppear:(BOOL)animated {
@@ -65,12 +84,82 @@
}
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
[super pushViewController:viewController animated:animated];
[self addNavigationBarItemsToViewController:viewController.navigationItem];
}
- (void)dismissAnimated {
// TODO tabs not closed on swipe down gesture
// Tabs are only closed if the done button is pressed; this
// allows you to leave a tab open by dragging down to dismiss
[FLEXTabList.sharedList closeTab:self];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
- (void)addNavigationBarItemsToViewController:(UINavigationItem *)navigationItem {
if (!self.presentingViewController) {
return;
}
// Check if a done item already exists
for (UIBarButtonItem *item in navigationItem.rightBarButtonItems) {
if (item.style == UIBarButtonItemStyleDone) {
return;
}
}
// Give root view controllers a Done button if it does not already have one
UIBarButtonItem *done = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(dismissAnimated)
];
// Prepend the button if other buttons exist already
NSArray *existingItems = navigationItem.rightBarButtonItems;
if (existingItems.count) {
navigationItem.rightBarButtonItems = [@[done] arrayByAddingObjectsFromArray:existingItems];
} else {
navigationItem.rightBarButtonItem = done;
}
// Keeps us from calling this method again on
// the same view controllers in -viewWillAppear:
self.didSetupPendingDismissButtons = YES;
}
- (void)addNavigationBarSwipeGesture {
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(handleNavigationBarSwipe:)
];
swipe.direction = UISwipeGestureRecognizerDirectionDown;
swipe.delegate = self;
self.navigationBarSwipeGesture = swipe;
[self.navigationBar addGestureRecognizer:swipe];
}
- (void)handleNavigationBarSwipe:(UISwipeGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateRecognized) {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)handleNavigationBarTap:(UIGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateRecognized) {
if (self.toolbarHidden) {
[self setToolbarHidden:NO animated:YES];
}
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)g2 {
if (g1 == self.navigationBarSwipeGesture && g2 == self.barHideOnSwipeGestureRecognizer) {
return YES;
}
return NO;
}
- (void)_gestureRecognizedInteractiveHide:(UIPanGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateRecognized) {
BOOL show = self.topViewController.toolbarItems.count;
@@ -3,12 +3,12 @@
// FLEX
//
// Created by Tanner on 7/5/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXTableView.h"
@class FLEXScopeCarousel, FLEXWindow;
@class FLEXScopeCarousel, FLEXWindow, FLEXTableViewSection;
typedef CGFloat FLEXDebounceInterval;
/// No delay, all events delivered
@@ -21,19 +21,34 @@ extern CGFloat const kFLEXDebounceForAsyncSearch;
extern CGFloat const kFLEXDebounceForExpensiveIO;
@protocol FLEXSearchResultsUpdating <NSObject>
/// A method to handle search query update events.
///
/// \c searchBarDebounceInterval is used to reduce the frequency at which this
/// method is called. This method is also called when the search bar becomes
/// the first responder, and when the selected search bar scope index changes.
- (void)updateSearchResults:(NSString *)newText;
@end
@interface FLEXTableViewController : UITableViewController <
UISearchResultsUpdating, UISearchControllerDelegate,
UISearchBarDelegate, FLEXSearchResultsUpdating
UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate
>
/// A grouped table view. Inset on iOS 13.
///
/// Simply calls into initWithStyle:
/// Simply calls into \c initWithStyle:
- (id)init;
/// Subclasses may override to configure the controller before \c viewDidLoad:
- (id)initWithStyle:(UITableViewStyle)style;
@property (nonatomic) FLEXTableView *tableView;
/// If your subclass conforms to \c FLEXSearchResultsUpdating
/// then this property is assigned to \c self automatically.
///
/// Setting \c filterDelegate will also set this property to that object.
@property (nonatomic, weak) id<FLEXSearchResultsUpdating> searchDelegate;
/// Defaults to NO.
///
/// Setting this to YES will initialize the carousel and the view.
@@ -74,7 +89,7 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
/// or it will not be respsected. Use this instead.
/// Defaults to NO.
@property (nonatomic) BOOL pinSearchBar;
/// By default, we will show the search bar's cancel button when
/// By default, we will show the search bar's cancel button when
/// search becomes active and hide it when search is dismissed.
///
/// Do not set the showsCancelButton property on the searchController's
@@ -87,31 +102,45 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
/// Otherwise, this is the selected index of the carousel, or NSNotFound if using neither.
@property (nonatomic) NSInteger selectedScope;
/// self.searchController.searchBar.text
@property (nonatomic, readonly) NSString *searchText;
@property (nonatomic, readonly, copy) NSString *searchText;
/// A totally optional delegate to forward search results updater calls to.
/// If a delegate is set, updateSearchResults: is not called on this view controller.
@property (nonatomic, weak ) id<FLEXSearchResultsUpdating> searchResultsUpdater;
/// If a delegate is set, updateSearchResults: is not called on this view controller.
@property (nonatomic, weak) id<FLEXSearchResultsUpdating> searchResultsUpdater;
/// self.view.window as a \c FLEXWindow
@property (nonatomic, readonly) FLEXWindow *window;
/// Subclasses should override to handle search query update events.
///
/// searchBarDebounceInterval is used to reduce the frequency at which this method is called.
/// This method is also called when the search bar becomes the first responder,
/// and when the selected search bar scope index changes.
- (void)updateSearchResults:(NSString *)newText;
/// Convenient for doing some async processor-intensive searching
/// in the background before updating the UI back on the main queue.
- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock;
/// Adds up to 3 additional items to the toolbar in right-to-left order.
///
/// That is, the first item in the given array will be the rightmost item behind
/// any existing toolbar items. By default, buttons for bookmarks and tabs are shown.
///
/// If you wish to have more control over how the buttons are arranged or which
/// buttons are displayed, you can access the properties for the pre-existing
/// toolbar items directly and manually set \c self.toolbarItems by overriding
/// the \c setupToolbarItems method below.
- (void)addToolbarItems:(NSArray<UIBarButtonItem *> *)items;
/// Subclasses may override. You should not need to call this method directly.
- (void)setupToolbarItems;
@property (nonatomic, readonly) UIBarButtonItem *shareToolbarItem;
@property (nonatomic, readonly) UIBarButtonItem *bookmarksToolbarItem;
@property (nonatomic, readonly) UIBarButtonItem *openTabsToolbarItem;
/// Whether or not to display the "share" icon in the middle of the toolbar. NO by default.
///
/// Turning this on after you have added custom toolbar items will
/// push off the leftmost toolbar item and shift the others leftward.
@property (nonatomic) BOOL showsShareToolbarItem;
/// Called when the share button is pressed.
/// Default implementation does nothign. Subclasses may override.
- (void)shareButtonPressed;
- (void)shareButtonPressed:(UIBarButtonItem *)sender;
/// Subclasses may call this to opt-out of all toolbar related behavior.
/// This is necessary if you want to disable the gesture which reveals the toolbar.
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 7/5/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -13,6 +13,7 @@
#import "FLEXScopeCarousel.h"
#import "FLEXTableView.h"
#import "FLEXUtility.h"
#import "FLEXResources.h"
#import "UIBarButtonItem+FLEX.h"
#import <objc/runtime.h>
@@ -30,10 +31,19 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
@property (nonatomic) BOOL didInitiallyRevealSearchBar;
@property (nonatomic) UITableViewStyle style;
@property (nonatomic) BOOL hasAppeared;
@property (nonatomic, readonly) UIView *tableHeaderViewContainer;
@property (nonatomic, readonly) BOOL manuallyDeactivateSearchOnDisappear;
@property (nonatomic) UIBarButtonItem *middleToolbarItem;
@property (nonatomic) UIBarButtonItem *middleLeftToolbarItem;
@property (nonatomic) UIBarButtonItem *leftmostToolbarItem;
@end
@implementation FLEXTableViewController
@dynamic tableView;
@synthesize showsShareToolbarItem = _showsShareToolbarItem;
@synthesize tableHeaderViewContainer = _tableHeaderViewContainer;
@synthesize automaticallyShowsSearchBarCancelButton = _automaticallyShowsSearchBarCancelButton;
@@ -59,6 +69,14 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
_searchBarDebounceInterval = kFLEXDebounceFast;
_showSearchBarInitially = YES;
_style = style;
_manuallyDeactivateSearchOnDisappear = ({
NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11;
});
// We will be our own search delegate if we implement this method
if ([self respondsToSelector:@selector(updateSearchResults:)]) {
self.searchDelegate = (id)self;
}
}
return self;
@@ -112,7 +130,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
carousel.selectedIndexChangedAction = ^(NSInteger idx) {
__typeof(self) self = weakSelf;
[self updateSearchResults:self.searchText];
[self.searchDelegate updateSearchResults:self.searchText];
};
// UITableView won't update the header size unless you reset the header view
@@ -136,7 +154,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
} else if (self.showsCarousel) {
return self.carousel.selectedIndex;
} else {
return NSNotFound;
return 0;
}
}
@@ -147,7 +165,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.carousel.selectedIndex = selectedScope;
}
[self updateSearchResults:self.searchText];
[self.searchDelegate updateSearchResults:self.searchText];
}
- (NSString *)searchText {
@@ -174,8 +192,6 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
_automaticallyShowsSearchBarCancelButton = value;
}
- (void)updateSearchResults:(NSString *)newText { }
- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *items = backgroundBlock();
@@ -205,6 +221,18 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.view = [FLEXTableView style:self.style];
self.tableView.dataSource = self;
self.tableView.delegate = self;
_shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed:));
_bookmarksToolbarItem = [UIBarButtonItem
flex_itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks)
];
_openTabsToolbarItem = [UIBarButtonItem
flex_itemWithImage:FLEXResources.openTabsIcon target:self action:@selector(showTabSwitcher)
];
self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace;
self.middleLeftToolbarItem = UIBarButtonItem.flex_fixedSpace;
self.middleToolbarItem = UIBarButtonItem.flex_fixedSpace;
}
- (void)viewDidLoad {
@@ -263,43 +291,108 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.didInitiallyRevealSearchBar = YES;
}
- (void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.manuallyDeactivateSearchOnDisappear && self.searchController.isActive) {
self.searchController.active = NO;
}
}
- (void)didMoveToParentViewController:(UIViewController *)parent {
[super didMoveToParentViewController:parent];
// Reset this since we are re-appearing under a new
// parent view controller and need to show it again
self.didInitiallyRevealSearchBar = NO;
}
#pragma mark - Private
#pragma mark - Toolbar, Public
- (void)setupToolbarItems {
UIBarButtonItem *emptySpaceOrShare = UIBarButtonItem.flex_fixedSpace;
if (self.showsShareToolbarItem) {
emptySpaceOrShare = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed));
if (!self.isViewLoaded) {
return;
}
self.toolbarItems = @[
UIBarButtonItem.flex_fixedSpace,
self.leftmostToolbarItem,
UIBarButtonItem.flex_flexibleSpace,
UIBarButtonItem.flex_fixedSpace,
self.middleLeftToolbarItem,
UIBarButtonItem.flex_flexibleSpace,
UIBarButtonItem.flex_fixedSpace,
self.middleToolbarItem,
UIBarButtonItem.flex_flexibleSpace,
emptySpaceOrShare,
self.bookmarksToolbarItem,
UIBarButtonItem.flex_flexibleSpace,
FLEXBarButtonItemSystem(Bookmarks, self, @selector(showBookmarks)),
UIBarButtonItem.flex_flexibleSpace,
FLEXBarButtonItemSystem(Organize, self, @selector(showTabSwitcher)),
self.openTabsToolbarItem,
];
for (UIBarButtonItem *item in self.toolbarItems) {
[item _setWidth:60];
// This does not work for anything but fixed spaces for some reason
// item.width = 60;
}
// Disable tabs entirely when not presented by FLEXExplorerViewController
UIViewController *presenter = self.navigationController.presentingViewController;
if (![presenter isKindOfClass:[FLEXExplorerViewController class]]) {
self.toolbarItems.lastObject.enabled = NO;
self.openTabsToolbarItem.enabled = NO;
}
}
- (void)addToolbarItems:(NSArray<UIBarButtonItem *> *)items {
if (self.showsShareToolbarItem) {
// Share button is in the middle, skip middle button
if (items.count > 0) {
self.middleLeftToolbarItem = items[0];
}
if (items.count > 1) {
self.leftmostToolbarItem = items[1];
}
} else {
// Add buttons right-to-left
if (items.count > 0) {
self.middleToolbarItem = items[0];
}
if (items.count > 1) {
self.middleLeftToolbarItem = items[1];
}
if (items.count > 2) {
self.leftmostToolbarItem = items[2];
}
}
[self setupToolbarItems];
}
- (void)setShowsShareToolbarItem:(BOOL)showShare {
if (_showsShareToolbarItem != showShare) {
_showsShareToolbarItem = showShare;
if (showShare) {
// Push out leftmost item
self.leftmostToolbarItem = self.middleLeftToolbarItem;
self.middleLeftToolbarItem = self.middleToolbarItem;
// Use share for middle
self.middleToolbarItem = self.shareToolbarItem;
} else {
// Remove share, shift custom items rightward
self.middleToolbarItem = self.middleLeftToolbarItem;
self.middleLeftToolbarItem = self.leftmostToolbarItem;
self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace;
}
}
[self setupToolbarItems];
}
- (void)shareButtonPressed:(UIBarButtonItem *)sender {
}
#pragma mark - Private
- (void)debounce:(void(^)(void))block {
[self.debounceTimer invalidate];
@@ -320,7 +413,6 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
}
self.tableView.tableHeaderView = self.tableView.tableHeaderView;
[self.tableView layoutIfNeeded];
}
- (void)addCarousel:(FLEXScopeCarousel *)carousel {
@@ -430,10 +522,6 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
[self presentViewController:nav animated:YES completion:nil];
}
- (void)shareButtonPressed {
}
#pragma mark - Search Bar
@@ -447,7 +535,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
if (self.searchResultsUpdater) {
[self.searchResultsUpdater updateSearchResults:text];
} else {
[self updateSearchResults:text];
[self.searchDelegate updateSearchResults:text];
}
};
@@ -486,7 +574,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
}
#pragma mark Table view
#pragma mark Table View
/// Not having a title in the first section looks weird with a rounded-corner table view style
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewSection.h"
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXSingleRowSection.h"
+20 -3
View File
@@ -3,10 +3,12 @@
// FLEX
//
// Created by Tanner on 1/29/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXUtility.h"
#import <UIKit/UIKit.h>
#import "FLEXMacros.h"
#import "NSArray+FLEX.h"
@class FLEXTableView;
NS_ASSUME_NONNULL_BEGIN
@@ -22,13 +24,17 @@ NS_ASSUME_NONNULL_BEGIN
@protected
/// Unused by default, use if you want
NSString *_title;
@private
__weak UITableView *_tableView;
NSInteger _sectionIndex;
}
#pragma mark - Data
/// A title to be displayed for the custom section.
/// Subclasses may override or use the \c _title ivar.
@property (nonatomic, readonly, nullable) NSString *title;
@property (nonatomic, readonly, nullable, copy) NSString *title;
/// The number of rows in this section. Subclasses must override.
/// This should not change until \c filterText is changed or \c reloadData is called.
@property (nonatomic, readonly) NSInteger numberOfRows;
@@ -55,6 +61,17 @@ NS_ASSUME_NONNULL_BEGIN
/// \c setFilterText: to call \c super and call \c reloadData.
- (void)reloadData;
/// Like \c reloadData, but optionally reloads the table view section
/// associated with this section object, if any. Do not override.
/// Do not call outside of the main thread.
- (void)reloadData:(BOOL)updateTable;
/// Provide a table view and section index to allow the section to efficiently reload
/// its own section of the table when something changes it. The table reference is
/// held weakly, and subclasses cannot access it or the index. Call this method again
/// if the section numbers have changed since you last called it.
- (void)setTable:(UITableView *)tableView section:(NSInteger)index;
#pragma mark - Row Selection
/// Whether the given row should be selectable, such as if tapping the cell
+18 -4
View File
@@ -3,11 +3,12 @@
// FLEX
//
// Created by Tanner on 1/29/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewSection.h"
#import "FLEXTableView.h"
#import "FLEXUtility.h"
#import "UIMenu+FLEX.h"
#pragma clang diagnostic push
@@ -21,6 +22,19 @@
- (void)reloadData { }
- (void)reloadData:(BOOL)updateTable {
[self reloadData];
if (updateTable) {
NSIndexSet *index = [NSIndexSet indexSetWithIndex:_sectionIndex];
[_tableView reloadSections:index withRowAnimation:UITableViewRowAnimationNone];
}
}
- (void)setTable:(UITableView *)tableView section:(NSInteger)index {
_tableView = tableView;
_sectionIndex = index;
}
- (NSDictionary<NSString *,Class> *)cellRegistrationMapping {
return nil;
}
@@ -67,7 +81,7 @@
return @"";
}
- (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender {
- (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender API_AVAILABLE(ios(13)) {
NSArray<NSString *> *copyItems = [self copyMenuItemsForRow:row];
NSAssert(copyItems.count % 2 == 0, @"copyMenuItemsForRow: should return an even list");
@@ -98,13 +112,13 @@
}
UIMenu *copyMenu = [UIMenu
inlineMenuWithTitle:@"Copy…"
flex_inlineMenuWithTitle:@"Copy…"
image:copyIcon
children:actions
];
if (collapseMenu) {
return @[[copyMenu collapsed]];
return @[[copyMenu flex_collapsed]];
} else {
return @[copyMenu];
}
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXCarouselCell.h"
@@ -25,8 +25,10 @@
_selectionIndicatorStripe = [UIView new];
self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
self.titleLabel.adjustsFontForContentSizeCategory = YES;
self.selectionIndicatorStripe.backgroundColor = self.tintColor;
if (@available(iOS 10, *)) {
self.titleLabel.adjustsFontForContentSizeCategory = YES;
}
[self.contentView addSubview:self.titleLabel];
[self.contentView addSubview:self.selectionIndicatorStripe];
@@ -45,7 +47,7 @@
if (self.selected) {
self.titleLabel.textColor = self.tintColor;
} else {
self.titleLabel.textColor = [FLEXColor deemphasizedTextColor];
self.titleLabel.textColor = FLEXColor.deemphasizedTextColor;
}
}
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXScopeCarousel.h"
@@ -29,7 +29,7 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [FLEXColor primaryBackgroundColor];
self.backgroundColor = FLEXColor.primaryBackgroundColor;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.translatesAutoresizingMaskIntoConstraints = YES;
_dynamicTypeHandlers = [NSMutableArray new];
@@ -76,11 +76,11 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
_dynamicTypeObserver = [NSNotificationCenter.defaultCenter
addObserverForName:UIContentSizeCategoryDidChangeNotification
object:nil queue:nil usingBlock:^(NSNotification *note) {
__typeof(self) self = weakSelf;
[self.collectionView setNeedsLayout];
[self setNeedsUpdateConstraints];
// Notify observers
__typeof(self) self = weakSelf;
for (void (^block)(FLEXScopeCarousel *) in self.dynamicTypeHandlers) {
block(self);
}
@@ -104,7 +104,7 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
// Draw hairline
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [FLEXColor hairlineColor].CGColor);
CGContextSetStrokeColorWithColor(context, FLEXColor.hairlineColor.CGColor);
CGContextSetLineWidth(context, width);
CGContextMoveToPoint(context, 0, rect.size.height - width);
CGContextAddLineToPoint(context, rect.size.width, rect.size.height - width);
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 12/27/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXMultilineTableViewCell.h"
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 12/27/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXCodeFontCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 1/23/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 1/23/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXKeyValueTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Ryan Olson on 2/13/15.
// Copyright (c) 2015 f. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Ryan Olson on 2/13/15.
// Copyright (c) 2015 f. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXMultilineTableViewCell.h"
@@ -26,7 +26,7 @@
}
+ (UIEdgeInsets)labelInsets {
return UIEdgeInsetsMake(10.0, 15.0, 10.0, 15.0);
return UIEdgeInsetsMake(10.0, 16.0, 10.0, 8.0);
}
+ (CGFloat)preferredHeightWithAttributedText:(NSAttributedString *)attributedText
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXSubtitleTableViewCell.h"
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
+2 -37
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewCell.h"
@@ -37,7 +37,7 @@
UIFont *cellFont = UIFont.flex_defaultTableCellFont;
self.titleLabel.font = cellFont;
self.subtitleLabel.font = cellFont;
self.subtitleLabel.textColor = [FLEXColor deemphasizedTextColor];
self.subtitleLabel.textColor = FLEXColor.deemphasizedTextColor;
self.titleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
self.subtitleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
@@ -54,39 +54,4 @@
return self.detailTextLabel;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
return [self._tableView _canPerformAction:action forCell:self sender:sender];
}
/// We use this to allow our table view to allow its delegate
/// to handle any action it chooses to support, without
/// explicitly implementing the method ourselves.
///
/// Alternative considered: override respondsToSelector
/// to return NO. I decided against this for simplicity's
/// sake. I see this as "fixing" a poorly designed API.
/// That approach would require lots of boilerplate to
/// make the menu appear above this cell.
- (void)forwardInvocation:(NSInvocation *)invocation {
// Must be unretained to avoid over-releasing
__unsafe_unretained id sender;
[invocation getArgument:&sender atIndex:2];
SEL action = invocation.selector;
// [self._tableView _performAction:action forCell:[self retain] sender:[sender retain]];
invocation.selector = @selector(_performAction:forCell:sender:);
[invocation setArgument:&action atIndex:2];
[invocation setArgument:(void *)&self atIndex:3];
[invocation setArgument:(void *)&sender atIndex:4];
[invocation invokeWithTarget:self._tableView];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
if ([self canPerformAction:selector withSender:nil]) {
return [self._tableView methodSignatureForSelector:@selector(_performAction:forCell:sender:)];
}
return [super methodSignatureForSelector:selector];
}
@end
+5 -1
View File
@@ -3,11 +3,13 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark Reuse identifiers
typedef NSString * FLEXTableViewCellReuseIdentifier;
@@ -42,3 +44,5 @@ extern FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell;
- (void)registerCells:(NSDictionary<NSString *, Class> *)registrationMapping;
@end
NS_ASSUME_NONNULL_END
+1 -25
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableView.h"
@@ -41,30 +41,6 @@ FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell = @"kFLEXCodeFontCell";
#endif
}
- (CGFloat)_heightForHeaderInSection:(NSInteger)section {
CGFloat height = [super _heightForHeaderInSection:section];
if (section == 0) {
NSString *title = [self _titleForHeaderInSection:section];
if (self.tableHeaderView) {
if (!@available(iOS 13, *)) {
return height - self.tableHeaderView.frame.size.height + 8;
}
// On iOS 13, returning an empty title for the table view
// messes with the height of the table view later on.
// We return a space to work around this.
else if ([title isEqualToString:@" "]) {
return height - self.tableHeaderView.frame.size.height + 5;
}
} else {
if (@available(iOS 13, *) && [title isEqualToString:@" "]) {
return 5;
}
}
}
return height;
}
#pragma mark - Initialization
+ (id)groupedTableView {
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/30/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/30/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputColorView.h"
@@ -234,7 +234,7 @@
[self updateWithColor:color];
}
} else {
[self updateWithColor:[UIColor clearColor]];
[self updateWithColor:UIColor.clearColor];
}
}
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Daniel Rodriguez Troitino on 2/14/15.
// Copyright (c) 2015 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Daniel Rodriguez Troitino on 2/14/15.
// Copyright (c) 2015 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputDateView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputFontView.h"
@@ -51,8 +51,8 @@
}
- (void)createAvailableFonts {
NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray array];
for (NSString *eachFontFamily in [UIFont familyNames]) {
NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray new];
for (NSString *eachFontFamily in UIFont.familyNames) {
for (NSString *eachFontName in [UIFont fontNamesForFamilyName:eachFontFamily]) {
[unsortedFontsArray addObject:eachFontName];
}
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/18/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/18/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputNotSupportedView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputNumberView.h"
@@ -17,6 +17,7 @@
self.inputTextView.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
self.targetSize = FLEXArgumentInputViewSizeSmall;
}
return self;
}
@@ -33,24 +34,29 @@
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value {
NSParameterAssert(type);
static NSArray<NSString *> *primitiveTypes = nil;
static NSArray<NSString *> *supportedTypes = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
primitiveTypes = @[@(@encode(char)),
@(@encode(int)),
@(@encode(short)),
@(@encode(long)),
@(@encode(long long)),
@(@encode(unsigned char)),
@(@encode(unsigned int)),
@(@encode(unsigned short)),
@(@encode(unsigned long)),
@(@encode(unsigned long long)),
@(@encode(float)),
@(@encode(double)),
@(@encode(long double))];
supportedTypes = @[
@FLEXEncodeClass(NSNumber),
@FLEXEncodeClass(NSDecimalNumber),
@(@encode(char)),
@(@encode(int)),
@(@encode(short)),
@(@encode(long)),
@(@encode(long long)),
@(@encode(unsigned char)),
@(@encode(unsigned int)),
@(@encode(unsigned short)),
@(@encode(unsigned long)),
@(@encode(unsigned long long)),
@(@encode(float)),
@(@encode(double)),
@(@encode(long double))
];
});
return type && [primitiveTypes containsObject:@(type)];
return type && [supportedTypes containsObject:@(type)];
}
@end
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputObjectView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputStringView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,12 +3,13 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputStructView.h"
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXTypeEncodingParser.h"
@interface FLEXArgumentInputStructView ()
@@ -21,7 +22,7 @@
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray array];
NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray new];
NSArray<NSString *> *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
[FLEXRuntimeUtility enumerateTypesInStructEncoding:typeEncoding usingBlock:^(NSString *structName,
const char *fieldTypeEncoding,
@@ -63,12 +64,8 @@
const char *structTypeEncoding = [inputValue objCType];
if (strcmp(self.typeEncoding.UTF8String, structTypeEncoding) == 0) {
NSUInteger valueSize = 0;
@try {
// NSGetSizeAndAlignment barfs on type encoding for bitfields.
NSGetSizeAndAlignment(structTypeEncoding, &valueSize, NULL);
} @catch (NSException *exception) { }
if (valueSize > 0) {
if (FLEXGetSizeAndAlignment(structTypeEncoding, &valueSize, NULL)) {
void *unboxedValue = malloc(valueSize);
[inputValue getValue:unboxedValue];
[FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName,
@@ -97,12 +94,8 @@
NSValue *boxedStruct = nil;
const char *structTypeEncoding = self.typeEncoding.UTF8String;
NSUInteger structSize = 0;
@try {
// NSGetSizeAndAlignment barfs on type encoding for bitfields.
NSGetSizeAndAlignment(structTypeEncoding, &structSize, NULL);
} @catch (NSException *exception) { }
if (structSize > 0) {
if (FLEXGetSizeAndAlignment(structTypeEncoding, &structSize, NULL)) {
void *unboxedStruct = malloc(structSize);
[FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName,
const char *fieldTypeEncoding,
@@ -182,15 +175,7 @@
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value {
NSParameterAssert(type);
if (type[0] == FLEXTypeEncodingStructBegin) {
// We cannot support anything with bitfields or structs,
// and this will throw an exception if it does
@try {
NSGetSizeAndAlignment(type, nil, nil);
} @catch (NSException *exception) {
return NO;
}
return YES;
return FLEXGetSizeAndAlignment(type, nil, nil);
}
return NO;
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputSwitchView.h"
@@ -25,7 +25,7 @@
if (self) {
self.inputTextView = [UITextView new];
self.inputTextView.font = [[self class] inputFont];
self.inputTextView.backgroundColor = [FLEXColor secondaryGroupedBackgroundColor];
self.inputTextView.backgroundColor = FLEXColor.secondaryGroupedBackgroundColor;
self.inputTextView.layer.cornerRadius = 10.f;
self.inputTextView.contentInset = UIEdgeInsetsMake(0, 5, 0, 0);
self.inputTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;
@@ -36,12 +36,12 @@
[self.inputTextView.layer setValue:@YES forKey:@"continuousCorners"];
} else {
self.inputTextView.layer.borderWidth = 1.f;
self.inputTextView.layer.borderColor = [FLEXColor borderColor].CGColor;
self.inputTextView.layer.borderColor = FLEXColor.borderColor.CGColor;
}
self.placeholderLabel = [UILabel new];
self.placeholderLabel.font = self.inputTextView.font;
self.placeholderLabel.textColor = [FLEXColor deemphasizedTextColor];
self.placeholderLabel.textColor = FLEXColor.deemphasizedTextColor;
self.placeholderLabel.numberOfLines = 0;
[self addSubview:self.inputTextView];
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/30/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/30/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -54,7 +54,7 @@
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.font = [[self class] titleFont];
_titleLabel.textColor = [FLEXColor primaryTextColor];
_titleLabel.textColor = FLEXColor.primaryTextColor;
_titleLabel.numberOfLines = 0;
[self addSubview:_titleLabel];
}
@@ -3,15 +3,19 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
@interface FLEXDefaultEditorViewController : FLEXFieldEditorViewController
NS_ASSUME_NONNULL_BEGIN
- (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
@interface FLEXDefaultEditorViewController : FLEXVariableEditorViewController
+ (BOOL)canEditDefaultWithValue:(id)currentValue;
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit;
+ (BOOL)canEditDefaultWithValue:(nullable id)currentValue;
@end
NS_ASSUME_NONNULL_END
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXDefaultEditorViewController.h"
@@ -15,23 +15,24 @@
@interface FLEXDefaultEditorViewController ()
@property (nonatomic, readonly) NSUserDefaults *defaults;
@property (nonatomic) NSString *key;
@property (nonatomic, readonly) NSString *key;
@end
@implementation FLEXDefaultEditorViewController
- (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key {
self = [super initWithTarget:defaults];
if (self) {
self.key = key;
self.title = @"Edit Default";
}
return self;
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit {
FLEXDefaultEditorViewController *editor = [self target:defaults data:key commitHandler:onCommit];
editor.title = @"Edit Default";
return editor;
}
- (NSUserDefaults *)defaults {
return [self.target isKindOfClass:[NSUserDefaults class]] ? self.target : nil;
return [_target isKindOfClass:[NSUserDefaults class]] ? _target : nil;
}
- (NSString *)key {
return _data;
}
- (void)viewDidLoad {
@@ -50,8 +51,6 @@
}
- (void)actionButtonPressed:(id)sender {
[super actionButtonPressed:sender];
id value = self.firstInputView.inputValue;
if (value) {
[self.defaults setObject:value forKey:self.key];
@@ -59,14 +58,16 @@
[self.defaults removeObjectForKey:self.key];
}
[self.defaults synchronize];
self.firstInputView.inputValue = [self.defaults objectForKey:self.key];
}
- (void)getterButtonPressed:(id)sender {
[super getterButtonPressed:sender];
id returnedObject = [self.defaults objectForKey:self.key];
[self exploreObjectOrPopViewController:returnedObject];
// Dismiss keyboard and handle committed changes
[super actionButtonPressed:sender];
// Go back after setting, but not for switches.
if (sender) {
[self.navigationController popViewControllerAnimated:YES];
} else {
self.firstInputView.inputValue = [self.defaults objectForKey:self.key];
}
}
+ (BOOL)canEditDefaultWithValue:(id)currentValue {
+1 -1
View File
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
+1 -1
View File
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXFieldEditorView.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXVariableEditorViewController.h"
@@ -15,9 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXFieldEditorViewController : FLEXVariableEditorViewController
/// @return nil if the property is readonly or if the type is unsupported
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property;
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit;
/// @return nil if the ivar type is unsupported
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar;
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit;
/// Subclasses can change the button title via the \c title property
@property (nonatomic, readonly) UIBarButtonItem *getterButton;
+16 -14
View File
@@ -3,13 +3,14 @@
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXFieldEditorView.h"
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXUtility.h"
#import "FLEXColor.h"
#import "UIBarButtonItem+FLEX.h"
@@ -29,21 +30,21 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target property:(FLEXProperty *)property {
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit {
id value = [property getValue:target];
if (![self canEditProperty:property onObject:target currentValue:value]) {
return nil;
}
FLEXFieldEditorViewController *editor = [self target:target];
editor.title = @"Property";
FLEXFieldEditorViewController *editor = [self target:target data:property commitHandler:onCommit];
editor.title = [@"Property: " stringByAppendingString:property.name];
editor.property = property;
return editor;
}
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar {
FLEXFieldEditorViewController *editor = [self target:target];
editor.title = @"Instance Variable";
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit];
editor.title = [@"Ivar: " stringByAppendingString:ivar.name];
editor.ivar = ivar;
return editor;
}
@@ -53,7 +54,7 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [FLEXColor groupedBackgroundColor];
self.view.backgroundColor = FLEXColor.groupedBackgroundColor;
// Create getter button
_getterButton = [[UIBarButtonItem alloc]
@@ -63,7 +64,7 @@
action:@selector(getterButtonPressed:)
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace, self.getterButton, self.setterButton
UIBarButtonItem.flex_flexibleSpace, self.getterButton, self.actionButton
];
// Configure input view
@@ -75,18 +76,16 @@
// Don't show a "set" button for switches; we mutate when the switch is flipped
if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
self.setterButton.enabled = NO;
self.setterButton.title = @"Flip the switch to call the setter";
self.actionButton.enabled = NO;
self.actionButton.title = @"Flip the switch to call the setter";
// Put getter button before setter button
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace, self.setterButton, self.getterButton
UIBarButtonItem.flex_flexibleSpace, self.actionButton, self.getterButton
];
}
}
- (void)actionButtonPressed:(id)sender {
[super actionButtonPressed:sender];
if (self.property) {
id userInputObject = self.firstInputView.inputValue;
NSArray *arguments = userInputObject ? @[userInputObject] : nil;
@@ -102,6 +101,9 @@
// this currently could and would assign NSArray to NSMutableArray
[self.ivar setValue:self.firstInputView.inputValue onObject:self.target];
}
// Dismiss keyboard and handle committed changes
[super actionButtonPressed:sender];
// Go back after setting, but not for switches.
if (sender) {
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXVariableEditorViewController.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXMethodCallingViewController.h"
@@ -16,7 +16,7 @@
#import "FLEXUtility.h"
@interface FLEXMethodCallingViewController ()
@property (nonatomic) FLEXMethod *method;
@property (nonatomic, readonly) FLEXMethod *method;
@end
@implementation FLEXMethodCallingViewController
@@ -28,10 +28,10 @@
- (id)initWithTarget:(id)target method:(FLEXMethod *)method {
NSParameterAssert(method.isInstanceMethod == !object_isClass(target));
self = [super initWithTarget:target];
self = [super initWithTarget:target data:method commitHandler:nil];
if (self) {
self.method = method;
self.title = method.isInstanceMethod ? @"Method" : @"Class Method";
self.title = method.isInstanceMethod ? @"Method: " : @"Class Method: ";
self.title = [self.title stringByAppendingString:method.selectorString];
}
return self;
@@ -40,7 +40,7 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.setterButton.title = @"Call";
self.actionButton.title = @"Call";
// Configure field editor view
self.fieldEditorView.argumentInputViews = [self argumentInputViews];
@@ -53,7 +53,7 @@
- (NSArray<FLEXArgumentInputView *> *)argumentInputViews {
Method method = self.method.objc_method;
NSArray *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:method];
NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray array];
NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray new];
unsigned int argumentIndex = kFLEXNumberOfImplicitArgs;
for (NSString *methodComponent in methodComponents) {
@@ -71,13 +71,11 @@
}
- (void)actionButtonPressed:(id)sender {
[super actionButtonPressed:sender];
// Gather arguments
NSMutableArray *arguments = [NSMutableArray array];
NSMutableArray *arguments = [NSMutableArray new];
for (FLEXArgumentInputView *inputView in self.fieldEditorView.argumentInputViews) {
// Use NSNull as a nil placeholder; it will be interpreted as nil
[arguments addObject:inputView.inputValue ?: [NSNull null]];
[arguments addObject:inputView.inputValue ?: NSNull.null];
}
// Call method
@@ -88,6 +86,9 @@
withArguments:arguments
error:&error
];
// Dismiss keyboard and handle committed changes
[super actionButtonPressed:sender];
// Display return value or error
if (error) {
@@ -102,4 +103,8 @@
}
}
- (FLEXMethod *)method {
return _data;
}
@end
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -11,25 +11,45 @@
@class FLEXFieldEditorView;
@class FLEXArgumentInputView;
/// Provides a screen for editing or configuring one or more variables.
@interface FLEXVariableEditorViewController : UIViewController
NS_ASSUME_NONNULL_BEGIN
+ (instancetype)target:(id)target;
- (id)initWithTarget:(id)target;
/// An abstract screen for editing or configuring one or more variables.
/// "Target" is the target of the edit operation, and "data" is the data
/// you want to mutate or pass to the target when the action is performed.
/// The action may be something like calling a method, setting an ivar, etc.
@interface FLEXVariableEditorViewController : UIViewController {
@protected
id _target;
_Nullable id _data;
void (^_Nullable _commitHandler)();
}
// Convenience accessor since many subclasses only use one input view
@property (nonatomic, readonly) FLEXArgumentInputView *firstInputView;
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
// For subclass use only.
@property (nonatomic, readonly) id target;
/// Convenience accessor since many subclasses only use one input view
@property (nonatomic, readonly, nullable) FLEXArgumentInputView *firstInputView;
@property (nonatomic, readonly) FLEXFieldEditorView *fieldEditorView;
/// Subclasses can change the button title via the button's \c title property
@property (nonatomic, readonly) UIBarButtonItem *setterButton;
@property (nonatomic, readonly) UIBarButtonItem *actionButton;
- (void)actionButtonPressed:(id)sender;
/// Subclasses should override to provide "set" functionality.
/// The commit handler--if present--is called here.
- (void)actionButtonPressed:(nullable id)sender;
/// Pushes an explorer view controller for the given object
/// or pops the current view controller.
- (void)exploreObjectOrPopViewController:(id)objectOrNil;
- (void)exploreObjectOrPopViewController:(nullable id)objectOrNil;
@end
NS_ASSUME_NONNULL_END
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXColor.h"
@@ -19,21 +19,22 @@
@interface FLEXVariableEditorViewController () <UIScrollViewDelegate>
@property (nonatomic) UIScrollView *scrollView;
@property (nonatomic) id target;
@end
@implementation FLEXVariableEditorViewController
#pragma mark - Initialization
+ (instancetype)target:(id)target {
return [[self alloc] initWithTarget:target];
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
return [[self alloc] initWithTarget:target data:data commitHandler:onCommit];
}
- (id)initWithTarget:(id)target {
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
self = [super init];
if (self) {
self.target = target;
_target = target;
_data = data;
_commitHandler = onCommit;
[NSNotificationCenter.defaultCenter
addObserver:self selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification object:nil
@@ -93,7 +94,7 @@
self.fieldEditorView.targetDescription = [NSString stringWithFormat:@"%@ %p", [self.target class], self.target];
[self.scrollView addSubview:self.fieldEditorView];
_setterButton = [[UIBarButtonItem alloc]
_actionButton = [[UIBarButtonItem alloc]
initWithTitle:@"Set"
style:UIBarButtonItemStyleDone
target:self
@@ -101,7 +102,7 @@
];
self.navigationController.toolbarHidden = NO;
self.toolbarItems = @[UIBarButtonItem.flex_flexibleSpace, self.setterButton];
self.toolbarItems = @[UIBarButtonItem.flex_flexibleSpace, self.actionButton];
}
- (void)viewWillLayoutSubviews {
@@ -120,6 +121,9 @@
- (void)actionButtonPressed:(id)sender {
// Subclasses can override
[self.fieldEditorView endEditing:YES];
if (_commitHandler) {
_commitHandler();
}
}
- (void)exploreObjectOrPopViewController:(id)objectOrNil {
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXBookmarkManager.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXBookmarksViewController.h"
@@ -68,10 +68,10 @@
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
self.toolbarItems = @[
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed)],
[UIBarButtonItem flex_itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
// We use a non-system done item because we change its title dynamically
[UIBarButtonItem doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
[UIBarButtonItem flex_doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
];
self.toolbarItems.firstObject.tintColor = FLEXColor.destructiveColor;
@@ -149,7 +149,7 @@
}
}
- (void)closeAllButtonPressed {
- (void)closeAllButtonPressed:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
NSInteger count = self.bookmarks.count;
NSString *title = FLEXPluralFormatString(count, @"Remove %@ bookmarks", @"Remove %@ bookmark");
@@ -158,7 +158,7 @@
[self toggleEditing];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
- (void)closeAll {
@@ -3,10 +3,10 @@
// Flipboard
//
// Created by Ryan Olson on 4/4/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXExplorerToolbar.h"
@class FLEXWindow;
@protocol FLEXExplorerViewControllerDelegate;
@@ -17,6 +17,8 @@
@property (nonatomic, weak) id <FLEXExplorerViewControllerDelegate> delegate;
@property (nonatomic, readonly) BOOL wantsWindowToBecomeKey;
@property (nonatomic, readonly) FLEXExplorerToolbar *explorerToolbar;
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
/// @brief Used to present (or dismiss) a modal view controller ("tool"), typically triggered by pressing a button in the toolbar.
@@ -3,12 +3,11 @@
// Flipboard
//
// Created by Ryan Olson on 4/4/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXExplorerViewController.h"
#import "FLEXExplorerToolbar.h"
#import "FLEXToolbarItem.h"
#import "FLEXExplorerToolbarItem.h"
#import "FLEXUtility.h"
#import "FLEXWindow.h"
#import "FLEXTabList.h"
@@ -20,8 +19,8 @@
#import "FLEXNetworkMITMViewController.h"
#import "FLEXTabsViewController.h"
#import "FLEXWindowManagerController.h"
static NSString *const kFLEXToolbarTopMarginDefaultsKey = @"com.flex.FLEXToolbar.topMargin";
#import "FLEXViewControllersViewController.h"
#import "NSUserDefaults+FLEX.h"
typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
FLEXExplorerModeDefault,
@@ -31,8 +30,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
@interface FLEXExplorerViewController () <FLEXHierarchyDelegate, UIAdaptivePresentationControllerDelegate>
@property (nonatomic) FLEXExplorerToolbar *explorerToolbar;
/// Tracks the currently active tool/mode
@property (nonatomic) FLEXExplorerMode currentMode;
@@ -48,6 +45,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
/// Only valid while a toolbar drag pan gesture is in progress.
@property (nonatomic) CGRect toolbarFrameBeforeDragging;
/// Only valid while a selected view pan gesture is in progress.
@property (nonatomic) CGFloat selectedViewLastPanX;
/// Borders of all the visible views in the hierarchy at the selection point.
/// The keys are NSValues with the corresponding view (nonretained).
@property (nonatomic) NSDictionary<NSValue *, UIView *> *outlineViewsForVisibleViews;
@@ -61,6 +61,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
/// A colored transparent overlay to indicate that the view is selected.
@property (nonatomic) UIView *selectedViewOverlay;
/// Used to actuate changes in view selection on iOS 10+
@property (nonatomic, readonly) UISelectionFeedbackGenerator *selectionFBG API_AVAILABLE(ios(10.0));
/// self.view.window as a \c FLEXWindow
@property (nonatomic, readonly) FLEXWindow *window;
@@ -77,7 +80,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.observedViews = [NSMutableSet set];
self.observedViews = [NSMutableSet new];
}
return self;
}
@@ -92,11 +95,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
[super viewDidLoad];
// Toolbar
self.explorerToolbar = [FLEXExplorerToolbar new];
_explorerToolbar = [FLEXExplorerToolbar new];
// Start the toolbar off below any bars that may be at the top of the view.
id toolbarOriginYDefault = [[NSUserDefaults standardUserDefaults] objectForKey:kFLEXToolbarTopMarginDefaultsKey];
CGFloat toolbarOriginY = toolbarOriginYDefault ? [toolbarOriginYDefault doubleValue] : 100;
CGFloat toolbarOriginY = NSUserDefaults.standardUserDefaults.flex_toolbarTopMargin;
CGRect safeArea = [self viewSafeArea];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(
@@ -122,6 +124,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
self.movePanGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleMovePan:)];
self.movePanGR.enabled = self.currentMode == FLEXExplorerModeMove;
[self.view addGestureRecognizer:self.movePanGR];
// Feedback
if (@available(iOS 10.0, *)) {
_selectionFBG = [UISelectionFeedbackGenerator new];
}
}
- (void)viewWillAppear:(BOOL)animated {
@@ -148,8 +155,13 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
// Commenting this out until I can figure out a better way to solve this
// if (self.window.isKeyWindow) {
// [self.window resignKeyWindow];
// }
UIViewController *viewControllerToAsk = [self viewControllerForRotationAndOrientation];
UIInterfaceOrientationMask supportedOrientations = [FLEXUtility infoPlistSupportedInterfaceOrientationsMask];
UIInterfaceOrientationMask supportedOrientations = FLEXUtility.infoPlistSupportedInterfaceOrientationsMask;
if (viewControllerToAsk && ![viewControllerToAsk isKindOfClass:[self class]]) {
supportedOrientations = [viewControllerToAsk supportedInterfaceOrientations];
}
@@ -368,24 +380,25 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)setupToolbarActions {
FLEXExplorerToolbar *toolbar = self.explorerToolbar;
NSDictionary<NSString *, FLEXToolbarItem *> *actionsToItems = @{
NSDictionary<NSString *, FLEXExplorerToolbarItem *> *actionsToItems = @{
NSStringFromSelector(@selector(selectButtonTapped:)): toolbar.selectItem,
NSStringFromSelector(@selector(hierarchyButtonTapped:)): toolbar.hierarchyItem,
NSStringFromSelector(@selector(recentButtonTapped:)): toolbar.recentItem,
NSStringFromSelector(@selector(moveButtonTapped:)): toolbar.moveItem,
NSStringFromSelector(@selector(globalsButtonTapped:)): toolbar.globalsItem,
NSStringFromSelector(@selector(closeButtonTapped:)): toolbar.closeItem,
};
[actionsToItems enumerateKeysAndObjectsUsingBlock:^(NSString *sel, FLEXToolbarItem *item, BOOL *stop) {
[actionsToItems enumerateKeysAndObjectsUsingBlock:^(NSString *sel, FLEXExplorerToolbarItem *item, BOOL *stop) {
[item addTarget:self action:NSSelectorFromString(sel) forControlEvents:UIControlEventTouchUpInside];
}];
}
- (void)selectButtonTapped:(FLEXToolbarItem *)sender {
- (void)selectButtonTapped:(FLEXExplorerToolbarItem *)sender {
[self toggleSelectTool];
}
- (void)hierarchyButtonTapped:(FLEXToolbarItem *)sender {
- (void)hierarchyButtonTapped:(FLEXExplorerToolbarItem *)sender {
[self toggleViewsTool];
}
@@ -394,25 +407,36 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return [UIApplication.sharedApplication valueForKey:statusBarString];
}
- (void)moveButtonTapped:(FLEXToolbarItem *)sender {
- (void)recentButtonTapped:(FLEXExplorerToolbarItem *)sender {
NSAssert(FLEXTabList.sharedList.activeTab, @"Must have active tab");
[self presentViewController:FLEXTabList.sharedList.activeTab animated:YES completion:nil];
}
- (void)moveButtonTapped:(FLEXExplorerToolbarItem *)sender {
[self toggleMoveTool];
}
- (void)globalsButtonTapped:(FLEXToolbarItem *)sender {
- (void)globalsButtonTapped:(FLEXExplorerToolbarItem *)sender {
[self toggleMenuTool];
}
- (void)closeButtonTapped:(FLEXToolbarItem *)sender {
- (void)closeButtonTapped:(FLEXExplorerToolbarItem *)sender {
self.currentMode = FLEXExplorerModeDefault;
[self.delegate explorerViewControllerDidFinish:self];
}
- (void)updateButtonStates {
// Move and details only active when an object is selected.
FLEXExplorerToolbar *toolbar = self.explorerToolbar;
toolbar.selectItem.selected = self.currentMode == FLEXExplorerModeSelect;
// Move only enabled when an object is selected.
BOOL hasSelectedObject = self.selectedView != nil;
self.explorerToolbar.moveItem.enabled = hasSelectedObject;
self.explorerToolbar.selectItem.selected = self.currentMode == FLEXExplorerModeSelect;
self.explorerToolbar.moveItem.selected = self.currentMode == FLEXExplorerModeMove;
toolbar.moveItem.enabled = hasSelectedObject;
toolbar.moveItem.selected = self.currentMode == FLEXExplorerModeMove;
// Recent only enabled when we have a last active tab
toolbar.recentItem.enabled = FLEXTabList.sharedList.activeTab != nil;
}
@@ -436,6 +460,17 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
initWithTarget:self action:@selector(handleToolbarDetailsTapGesture:)
];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:self.detailsTapGR];
// Swipe gestures for selecting deeper / higher views at a point
UIPanGestureRecognizer *leftSwipe = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
];
// UIPanGestureRecognizer *rightSwipe = [[UIPanGestureRecognizer alloc]
// initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
// ];
// leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
// rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:leftSwipe];
// [toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
// Long press gesture to present tabs manager
[toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
@@ -446,6 +481,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
[toolbar.selectItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(handleToolbarWindowManagerGesture:)
]];
// Long press gesture to present view controllers at tap
[toolbar.hierarchyItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(handleToolbarShowViewControllersGesture:)
]];
}
- (void)handleToolbarPanGesture:(UIPanGestureRecognizer *)panGR {
@@ -486,10 +526,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
self.explorerToolbar.frame = unconstrainedFrame;
[NSUserDefaults.standardUserDefaults
setDouble:unconstrainedFrame.origin.y forKey:kFLEXToolbarTopMarginDefaultsKey
];
NSUserDefaults.standardUserDefaults.flex_toolbarTopMargin = unconstrainedFrame.origin.y;
}
- (void)handleToolbarHintTapGesture:(UITapGestureRecognizer *)tapGR {
@@ -521,21 +558,40 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
- (void)handleToolbarShowTabsGesture:(UILongPressGestureRecognizer *)sender {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
[super presentViewController:[[UINavigationController alloc]
initWithRootViewController:[FLEXTabsViewController new]
] animated:YES completion:nil];
if (sender.state == UIGestureRecognizerStateBegan) {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
// Don't use FLEXNavigationController because the tab viewer itself is not a tab
[super presentViewController:[[UINavigationController alloc]
initWithRootViewController:[FLEXTabsViewController new]
] animated:YES completion:nil];
}
}
- (void)handleToolbarWindowManagerGesture:(UILongPressGestureRecognizer *)sender {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
[super presentViewController:[[UINavigationController alloc]
initWithRootViewController:[FLEXWindowManagerController new]
] animated:YES completion:nil];
if (sender.state == UIGestureRecognizerStateBegan) {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
[super presentViewController:[FLEXNavigationController
withRootViewController:[FLEXWindowManagerController new]
] animated:YES completion:nil];
}
}
- (void)handleToolbarShowViewControllersGesture:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan && self.viewsAtTapPoint.count) {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
UIViewController *list = [FLEXViewControllersViewController
controllersForViews:self.viewsAtTapPoint
];
[self presentViewController:
[FLEXNavigationController withRootViewController:list
] animated:YES completion:nil];
}
}
@@ -553,6 +609,57 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
}
- (void)handleChangeViewAtPointGesture:(UIPanGestureRecognizer *)sender {
NSInteger max = self.viewsAtTapPoint.count - 1;
NSInteger currentIdx = [self.viewsAtTapPoint indexOfObject:self.selectedView];
CGFloat locationX = [sender locationInView:self.view].x;
// Track the pan gesture: every N points we move along the X axis,
// actuate some haptic feedback and move up or down the hierarchy.
// We only store the "last" location when we've met the threshold.
// We only change the view and actuate feedback if the view selection
// changes; that is, as long as we don't go outside or under the array.
switch (sender.state) {
case UIGestureRecognizerStateBegan: {
self.selectedViewLastPanX = locationX;
break;
}
case UIGestureRecognizerStateChanged: {
static CGFloat kNextLevelThreshold = 20.f;
CGFloat lastX = self.selectedViewLastPanX;
NSInteger newSelection = currentIdx;
// Left, go down the hierarchy
if (locationX < lastX && (lastX - locationX) >= kNextLevelThreshold) {
// Choose a new view index up to the max index
newSelection = MIN(max, currentIdx + 1);
self.selectedViewLastPanX = locationX;
}
// Right, go up the hierarchy
else if (lastX < locationX && (locationX - lastX) >= kNextLevelThreshold) {
// Choose a new view index down to the min index
newSelection = MAX(0, currentIdx - 1);
self.selectedViewLastPanX = locationX;
}
if (currentIdx != newSelection) {
self.selectedView = self.viewsAtTapPoint[newSelection];
[self actuateSelectionChangedFeedback];
}
break;
}
default: break;
}
}
- (void)actuateSelectionChangedFeedback {
if (@available(iOS 10.0, *)) {
[self.selectionFBG selectionChanged];
}
}
- (void)updateOutlineViewsForSelectionPoint:(CGPoint)selectionPointInWindow {
[self removeAndClearOutlineViews];
@@ -596,8 +703,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
- (NSArray<UIView *> *)viewsAtPoint:(CGPoint)tapPointInWindow skipHiddenViews:(BOOL)skipHidden {
NSMutableArray<UIView *> *views = [NSMutableArray array];
for (UIWindow *window in [FLEXUtility allWindows]) {
NSMutableArray<UIView *> *views = [NSMutableArray new];
for (UIWindow *window in FLEXUtility.allWindows) {
// Don't include the explorer's own window or subviews.
if (window != self.view.window && [window pointInside:tapPointInWindow withEvent:nil]) {
[views addObject:window];
@@ -613,8 +720,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// Select in the window that would handle the touch, but don't just use the result of
// hitTest:withEvent: so we can still select views with interaction disabled.
// Default to the the application's key window if none of the windows want the touch.
UIWindow *windowForSelection = [UIApplication.sharedApplication keyWindow];
for (UIWindow *window in [FLEXUtility allWindows].reverseObjectEnumerator) {
UIWindow *windowForSelection = UIApplication.sharedApplication.keyWindow;
for (UIWindow *window in FLEXUtility.allWindows.reverseObjectEnumerator) {
// Ignore the explorer's own window.
if (window != self.view.window) {
if ([window hitTest:tapPointInWindow withEvent:nil]) {
@@ -631,7 +738,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (NSArray<UIView *> *)recursiveSubviewsAtPoint:(CGPoint)pointInView
inView:(UIView *)view
skipHiddenViews:(BOOL)skipHidden {
NSMutableArray<UIView *> *subviewsAtPoint = [NSMutableArray array];
NSMutableArray<UIView *> *subviewsAtPoint = [NSMutableArray new];
for (UIView *subview in view.subviews) {
BOOL isHidden = subview.hidden || subview.alpha < 0.01;
if (skipHidden && isHidden) {
@@ -782,16 +889,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
// Back up and replace the UIMenuController items
// Edit: no longer replacing the items, but still backing them
// up in case we start replacing them again in the future
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
// Initialize custom menu items for explorer screen
UIMenuItem *copyObjectAddress = [[UIMenuItem alloc]
initWithTitle:@"Copy Address"
action:NSSelectorFromString(@"copyObjectAddress:")
];
UIMenuController.sharedMenuController.menuItems = @[copyObjectAddress];
[UIMenuController.sharedMenuController update];
// Show the view controller.
// Show the view controller
[super presentViewController:toPresent animated:animated completion:completion];
}
@@ -811,6 +913,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// scroll to top, but below FLEX otherwise for exploration.
[self statusWindow].windowLevel = UIWindowLevelStatusBar;
[self updateButtonStates];
[super dismissViewControllerAnimated:animated completion:completion];
}
@@ -845,8 +949,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)toggleMoveTool {
if (self.currentMode == FLEXExplorerModeMove) {
self.currentMode = FLEXExplorerModeDefault;
} else {
self.currentMode = FLEXExplorerModeSelect;
} else if (self.currentMode == FLEXExplorerModeSelect && self.selectedView) {
self.currentMode = FLEXExplorerModeMove;
}
}
@@ -0,0 +1,19 @@
//
// FLEXViewControllersViewController.h
// FLEX
//
// Created by Tanner Bennett on 2/13/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXFilteringTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXViewControllersViewController : FLEXFilteringTableViewController
+ (instancetype)controllersForViews:(NSArray<UIView *> *)views;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,79 @@
//
// FLEXViewControllersViewController.m
// FLEX
//
// Created by Tanner Bennett on 2/13/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXViewControllersViewController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXMutableListSection.h"
#import "FLEXUtility.h"
@interface FLEXViewControllersViewController ()
@property (nonatomic, readonly) FLEXMutableListSection *section;
@property (nonatomic, readonly) NSArray<UIViewController *> *controllers;
@end
@implementation FLEXViewControllersViewController
@dynamic sections, allSections;
#pragma mark - Initialization
+ (instancetype)controllersForViews:(NSArray<UIView *> *)views {
return [[self alloc] initWithViews:views];
}
- (id)initWithViews:(NSArray<UIView *> *)views {
NSParameterAssert(views.count);
self = [self initWithStyle:UITableViewStylePlain];
if (self) {
_controllers = [views flex_mapped:^id(UIView *view, NSUInteger idx) {
return [FLEXUtility viewControllerForView:view];
}];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"View Controllers at Tap";
self.showsSearchBar = YES;
[self disableToolbar];
}
- (NSArray<FLEXTableViewSection *> *)makeSections {
_section = [FLEXMutableListSection list:self.controllers
cellConfiguration:^(UITableViewCell *cell, UIViewController *controller, NSInteger row) {
cell.textLabel.text = [NSString
stringWithFormat:@"%@ — %p", NSStringFromClass(controller.class), controller
];
cell.detailTextLabel.text = controller.view.description;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.lineBreakMode = NSLineBreakByTruncatingTail;
} filterMatcher:^BOOL(NSString *filterText, UIViewController *controller) {
return [NSStringFromClass(controller.class) localizedCaseInsensitiveContainsString:filterText];
}];
self.section.selectionHandler = ^(UIViewController *host, UIViewController *controller) {
[host.navigationController pushViewController:
[FLEXObjectExplorerFactory explorerViewControllerForObject:controller]
animated:YES];
};
self.section.customTitle = @"View Controllers";
return @[self.section];
}
#pragma mark - Private
- (void)dismissAnimated {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
+1 -1
View File
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 4/13/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
+1 -1
View File
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 4/13/14.
// Copyright (c) 2014 Flipboard. All rights reserved.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXWindow.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -3,12 +3,13 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXWindowManagerController.h"
#import "FLEXManager+Private.h"
#import "FLEXUtility.h"
#import "FLEXObjectExplorerFactory.h"
@interface FLEXWindowManagerController ()
@property (nonatomic) UIWindow *keyWindow;
@@ -17,6 +18,7 @@
@property (nonatomic, copy) NSArray<NSString *> *windowSubtitles;
@property (nonatomic, copy) NSArray<UIScene *> *scenes API_AVAILABLE(ios(13));
@property (nonatomic, copy) NSArray<NSString *> *sceneSubtitles;
@property (nonatomic, copy) NSArray<NSArray *> *sections;
@end
@implementation FLEXWindowManagerController
@@ -36,10 +38,6 @@
}
[self disableToolbar];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissAnimated)
];
[self reloadData];
}
@@ -49,18 +47,25 @@
- (void)reloadData {
self.keyWindow = UIApplication.sharedApplication.keyWindow;
self.windows = UIApplication.sharedApplication.windows;
self.keyWindowSubtitle = self.windowSubtitles[[self.windows indexOfObject:self.keyWindow]];
self.windowSubtitles = [self.windows flex_mapped:^id(UIWindow *window, NSUInteger idx) {
return [NSString stringWithFormat:@"Level: %@ — Root: %@",
@(window.windowLevel), window.rootViewController
];
}];
self.keyWindowSubtitle = self.windowSubtitles[[self.windows indexOfObject:self.keyWindow]];
if (@available(iOS 13, *)) {
self.scenes = UIApplication.sharedApplication.connectedScenes.allObjects;
self.sceneSubtitles = [self.scenes flex_mapped:^id(UIScene *scene, NSUInteger idx) {
return [self sceneDescription:scene];
}];
self.sections = @[@[self.keyWindow], self.windows, self.scenes];
} else {
self.sections = @[@[self.keyWindow], self.windows];
}
[self.tableView reloadData];
}
- (void)dismissAnimated {
@@ -137,27 +142,11 @@
#pragma mark - Table View Data Source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (@available(iOS 13, *)) {
return 3;
}
return 2;
return self.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0:
return 1;
case 1:
return self.windows.count;
case 2:
if (@available(iOS 13, *)) {
return self.scenes.count;
}
}
@throw NSInternalInconsistencyException;
return 0;
return self.sections[section].count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
@@ -172,6 +161,9 @@
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXDetailCell forIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryDetailButton;
cell.textLabel.lineBreakMode = NSLineBreakByTruncatingTail;
UIWindow *window = nil;
NSString *subtitle = nil;
@@ -195,7 +187,8 @@
cell.textLabel.text = window.description;
cell.detailTextLabel.text = [NSString
stringWithFormat:@"Level: %@ — Root: %@", @(window.windowLevel), window.rootViewController
stringWithFormat:@"Level: %@ — Root: %@",
@((NSInteger)window.windowLevel), window.rootViewController.class
];
return cell;
@@ -300,4 +293,10 @@
} showFrom:self];
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)ip {
[self.navigationController pushViewController:
[FLEXObjectExplorerFactory explorerViewControllerForObject:self.sections[ip.section][ip.row]]
animated:YES];
}
@end
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/1/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/1/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTabList.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/4/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/4/20.
// Copyright © 2020 Flipboard. All rights reserved.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTabsViewController.h"
@@ -42,7 +42,6 @@
self.navigationController.hidesBarsOnSwipe = NO;
self.tableView.allowsMultipleSelectionDuringEditing = YES;
[FLEXTabList.sharedList updateSnapshotForActiveTab];
[self reloadData:NO];
}
@@ -51,6 +50,19 @@
[self setupDefaultBarItems];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Instead of updating the active snapshot before we present,
// we update it after we present to avoid pre-presenation latency
dispatch_async(dispatch_get_main_queue(), ^{
[FLEXTabList.sharedList updateSnapshotForActiveTab];
[self reloadData:NO];
[self.tableView reloadData];
});
}
#pragma mark - Private
/// @param trackActiveTabDelta whether to check if the active
@@ -66,6 +78,10 @@
if (oldActiveIndex != list.activeTabIndex && list.activeTabIndex != NSNotFound) {
self.presentNewActiveTabOnDismiss = YES;
activeTabDidChange = YES;
} else if (self.presentNewActiveTabOnDismiss) {
// If we had something to present before, now we don't
// (i.e. activeTabIndex == NSNotFound)
self.presentNewActiveTabOnDismiss = NO;
}
}
@@ -93,7 +109,7 @@
self.toolbarItems = @[
UIBarButtonItem.flex_fixedSpace,
UIBarButtonItem.flex_flexibleSpace,
FLEXBarButtonItemSystem(Add, self, @selector(addTabButtonPressed)),
FLEXBarButtonItemSystem(Add, self, @selector(addTabButtonPressed:)),
UIBarButtonItem.flex_flexibleSpace,
FLEXBarButtonItemSystem(Edit, self, @selector(toggleEditing)),
];
@@ -105,12 +121,12 @@
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
self.toolbarItems = @[
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed)],
[UIBarButtonItem flex_itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem disabledSystemItem:UIBarButtonSystemItemAdd],
[UIBarButtonItem flex_disabledSystemItem:UIBarButtonSystemItemAdd],
UIBarButtonItem.flex_flexibleSpace,
// We use a non-system done item because we change its title dynamically
[UIBarButtonItem doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
[UIBarButtonItem flex_doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
];
self.toolbarItems.firstObject.tintColor = FLEXColor.destructiveColor;
@@ -128,6 +144,7 @@
return presenter;
}
#pragma mark Button Actions
- (void)dismissAnimated {
@@ -176,7 +193,7 @@
}
}
- (void)addTabButtonPressed {
- (void)addTabButtonPressed:(UIBarButtonItem *)sender {
if (FLEXBookmarkManager.bookmarks.count) {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
make.title(@"New Tab");
@@ -191,7 +208,7 @@
] animated:YES completion:nil];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
} else {
// No bookmarks, just open the main menu
[self addTabAndDismiss:[FLEXNavigationController
@@ -207,7 +224,7 @@
}];
}
- (void)closeAllButtonPressed {
- (void)closeAllButtonPressed:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
NSInteger count = self.openTabs.count;
NSString *title = FLEXPluralFormatString(count, @"Close %@ tabs", @"Close %@ tab");
@@ -216,7 +233,7 @@
[self toggleEditing];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
- (void)closeAll {
@@ -233,6 +250,7 @@
[self.tableView deleteRowsAtIndexPaths:allRows withRowAnimation:UITableViewRowAnimationAutomatic];
}
#pragma mark - Table View Data Source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
@@ -248,6 +266,7 @@
cell.detailTextLabel.text = FLEXPluralString(tab.viewControllers.count, @"pages", @"page");
if (!cell.tag) {
cell.textLabel.lineBreakMode = NSLineBreakByTruncatingTail;
cell.textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
cell.detailTextLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
cell.tag = 1;
@@ -262,6 +281,7 @@
return cell;
}
#pragma mark - Table View Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+27
View File
@@ -0,0 +1,27 @@
//
// FLEX-Categories.h
// FLEX
//
// Created by Tanner on 3/12/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <FLEX/UIBarButtonItem+FLEX.h>
#import <FLEX/CALayer+FLEX.h>
#import <FLEX/UIFont+FLEX.h>
#import <FLEX/UIGestureRecognizer+Blocks.h>
#import <FLEX/UIView+FLEX_Layout.h>
#import <FLEX/UIPasteboard+FLEX.h>
#import <FLEX/UIMenu+FLEX.h>
#import <FLEX/UITextField+Range.h>
#import <FLEX/NSObject+FLEX_Reflection.h>
#import <FLEX/NSArray+FLEX.h>
#import <FLEX/NSDictionary+ObjcRuntime.h>
#import <FLEX/NSString+ObjcRuntime.h>
#import <FLEX/NSString+FLEX.h>
#import <FLEX/NSUserDefaults+FLEX.h>
#import <FLEX/NSMapTable+FLEX_Subscripting.h>
#import <FLEX/NSTimer+FLEX.h>
+23
View File
@@ -0,0 +1,23 @@
//
// FLEX-Core.h
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXFilteringTableViewController.h>
#import <FLEX/FLEXNavigationController.h>
#import <FLEX/FLEXTableViewController.h>
#import <FLEX/FLEXTableView.h>
#import <FLEX/FLEXSingleRowSection.h>
#import <FLEX/FLEXTableViewSection.h>
#import <FLEX/FLEXCodeFontCell.h>
#import <FLEX/FLEXSubtitleTableViewCell.h>
#import <FLEX/FLEXTableViewCell.h>
#import <FLEX/FLEXMultilineTableViewCell.h>
#import <FLEX/FLEXKeyValueTableViewCell.h>
#import <FLEX/FLEXScopeCarousel.h>
+30
View File
@@ -0,0 +1,30 @@
//
// FLEX-ObjectExploring.h
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXObjectExplorerFactory.h>
#import <FLEX/FLEXObjectExplorerViewController.h>
#import <FLEX/FLEXObjectExplorer.h>
#import <FLEX/FLEXShortcut.h>
#import <FLEX/FLEXShortcutsFactory+Defaults.h>
#import <FLEX/FLEXShortcutsSection.h>
#import <FLEX/FLEXBlockShortcuts.h>
#import <FLEX/FLEXBundleShortcuts.h>
#import <FLEX/FLEXClassShortcuts.h>
#import <FLEX/FLEXImageShortcuts.h>
#import <FLEX/FLEXLayerShortcuts.h>
#import <FLEX/FLEXViewControllerShortcuts.h>
#import <FLEX/FLEXViewShortcuts.h>
#import <FLEX/FLEXCollectionContentSection.h>
#import <FLEX/FLEXColorPreviewSection.h>
#import <FLEX/FLEXDefaultsContentSection.h>
#import <FLEX/FLEXMetadataSection.h>
#import <FLEX/FLEXMutableListSection.h>
#import <FLEX/FLEXObjectInfoSection.h>
+25
View File
@@ -0,0 +1,25 @@
//
// FLEX-Runtime.h
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXObjcInternal.h>
#import <FLEX/FLEXRuntimeSafety.h>
#import <FLEX/FLEXBlockDescription.h>
#import <FLEX/FLEXTypeEncodingParser.h>
#import <FLEX/FLEXMirror.h>
#import <FLEX/FLEXProtocol.h>
#import <FLEX/FLEXProperty.h>
#import <FLEX/FLEXIvar.h>
#import <FLEX/FLEXMethodBase.h>
#import <FLEX/FLEXMethod.h>
#import <FLEX/FLEXPropertyAttributes.h>
#import <FLEX/FLEXRuntime+Compare.h>
#import <FLEX/FLEXRuntime+UIKitHelpers.h>
#import <FLEX/FLEXProtocolBuilder.h>
#import <FLEX/FLEXClassBuilder.h>
+15 -1
View File
@@ -3,9 +3,23 @@
// FLEX
//
// Created by Eric Horacek on 7/18/15.
// Copyright (c) 2015 Flipboard. All rights reserved.
// Modified by Tanner Bennett on 3/12/20.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXManager.h>
#import <FLEX/FLEXManager+Extensibility.h>
#import <FLEX/FLEXManager+Networking.h>
#import <FLEX/FLEXExplorerToolbar.h>
#import <FLEX/FLEXExplorerToolbarItem.h>
#import <FLEX/FLEXGlobalsEntry.h>
#import <FLEX/FLEX-Core.h>
#import <FLEX/FLEX-Runtime.h>
#import <FLEX/FLEX-Categories.h>
#import <FLEX/FLEX-ObjectExploring.h>
#import <FLEX/FLEXMacros.h>
#import <FLEX/FLEXAlert.h>
#import <FLEX/FLEXResources.h>
@@ -0,0 +1,19 @@
//
// FLEXDBQueryRowCell.h
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
//
#import <UIKit/UIKit.h>
extern NSString * const kFLEXDBQueryRowCellReuse;
@interface FLEXDBQueryRowCell : UITableViewCell
/// An array of NSString, NSNumber, or NSData objects
@property (nonatomic) NSArray *data;
@end
@@ -0,0 +1,74 @@
//
// FLEXDBQueryRowCell.m
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
//
#import "FLEXDBQueryRowCell.h"
#import "FLEXMultiColumnTableView.h"
#import "NSArray+FLEX.h"
#import "UIFont+FLEX.h"
#import "FLEXColor.h"
NSString * const kFLEXDBQueryRowCellReuse = @"kFLEXDBQueryRowCellReuse";
@interface FLEXDBQueryRowCell ()
@property (nonatomic) NSInteger columnCount;
@property (nonatomic) NSArray<UILabel *> *labels;
@end
@implementation FLEXDBQueryRowCell
- (void)setData:(NSArray *)data {
_data = data;
self.columnCount = data.count;
[self.labels flex_forEach:^(UILabel *label, NSUInteger idx) {
id content = self.data[idx];
if ([content isKindOfClass:[NSString class]]) {
label.text = content;
} else if (content == NSNull.null) {
label.text = @"<null>";
label.textColor = FLEXColor.deemphasizedTextColor;
} else {
label.text = [content description];
}
}];
}
- (void)setColumnCount:(NSInteger)columnCount {
if (columnCount != _columnCount) {
_columnCount = columnCount;
// Remove existing labels
for (UILabel *l in self.labels) {
[l removeFromSuperview];
}
// Create new labels
self.labels = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
UILabel *label = [UILabel new];
label.font = UIFont.flex_defaultTableCellFont;
label.textAlignment = NSTextAlignmentLeft;
[self.contentView addSubview:label];
return label;
}];
}
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat width = self.contentView.frame.size.width / self.labels.count;
CGFloat height = self.contentView.frame.size.height;
[self.labels flex_forEach:^(UILabel *label, NSUInteger i) {
label.frame = CGRectMake(width * i + 5, 0, (width - 10), height);
}];
}
@end
@@ -12,15 +12,23 @@
// which Flying Meat Inc. licenses this file to you.
#import <Foundation/Foundation.h>
#import "FLEXSQLResult.h"
/// Conformers should automatically open and close the database
@protocol FLEXDatabaseManager <NSObject>
@required
- (instancetype)initWithPath:(NSString*)path;
- (BOOL)open;
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables;
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName;
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName;
/// @return \c nil if the database couldn't be opened
+ (instancetype)managerForDatabase:(NSString *)path;
/// @return a list of all table names
- (NSArray<NSString *> *)queryAllTables;
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName;
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName;
@optional
- (FLEXSQLResult *)executeStatement:(NSString *)SQLStatement;
@end
@@ -14,8 +14,8 @@
@protocol FLEXMultiColumnTableViewDelegate <NSObject>
@required
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapLabelWithText:(NSString *)text;
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType;
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didSelectRow:(NSInteger)row;
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didSelectHeaderForColumn:(NSInteger)column sortType:(FLEXTableColumnHeaderSortType)sortType;
@end
@@ -25,10 +25,9 @@
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView;
- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView;
- (NSString *)columnNameInColumn:(NSInteger)column;
- (NSString *)rowNameInRow:(NSInteger)row;
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row;
- (NSArray *)contentAtRow:(NSInteger)row;
- (NSString *)columnTitle:(NSInteger)column;
- (NSString *)rowTitle:(NSInteger)row;
- (NSArray<NSString *> *)contentForRow:(NSInteger)row;
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView widthForContentCellInColumn:(NSInteger)column;
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView heightForContentCellInRow:(NSInteger)row;
@@ -40,8 +39,8 @@
@interface FLEXMultiColumnTableView : UIView
@property (nonatomic, weak) id<FLEXMultiColumnTableViewDataSource>dataSource;
@property (nonatomic, weak) id<FLEXMultiColumnTableViewDelegate>delegate;
@property (nonatomic, weak) id<FLEXMultiColumnTableViewDataSource> dataSource;
@property (nonatomic, weak) id<FLEXMultiColumnTableViewDelegate> delegate;
- (void)reloadData;
@@ -7,11 +7,13 @@
//
#import "FLEXMultiColumnTableView.h"
#import "FLEXTableContentCell.h"
#import "FLEXDBQueryRowCell.h"
#import "FLEXTableLeftCell.h"
#import "FLEXColor.h"
@interface FLEXMultiColumnTableView ()
<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate, FLEXTableContentCellDelegate>
@interface FLEXMultiColumnTableView () <
UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate
>
@property (nonatomic) UIScrollView *contentScrollView;
@property (nonatomic) UIScrollView *headerScrollView;
@@ -19,35 +21,49 @@
@property (nonatomic) UITableView *contentTableView;
@property (nonatomic) UIView *leftHeader;
@property (nonatomic) NSDictionary<NSString *, NSNumber *> *sortStatusDict;
/// \c NSNotFound if no column selected
@property (nonatomic) NSInteger sortColumn;
@property (nonatomic) FLEXTableColumnHeaderSortType sortType;
@property (nonatomic) NSArray *rowData;
@property (nonatomic, readonly) NSInteger numberOfColumns;
@property (nonatomic, readonly) NSInteger numberOfRows;
@property (nonatomic, readonly) CGFloat topHeaderHeight;
@property (nonatomic, readonly) CGFloat leftHeaderWidth;
@property (nonatomic, readonly) CGFloat columnMargin;
@end
static const CGFloat kColumnMargin = 1;
@implementation FLEXMultiColumnTableView
#pragma mark - Initialization
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self loadUI];
self.autoresizingMask |= UIViewAutoresizingFlexibleWidth;
self.autoresizingMask |= UIViewAutoresizingFlexibleHeight;
self.autoresizingMask |= UIViewAutoresizingFlexibleTopMargin;
self.backgroundColor = FLEXColor.groupedBackgroundColor;
[self loadHeaderScrollView];
[self loadContentScrollView];
[self loadLeftView];
}
return self;
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
[self reloadData];
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
CGFloat topheaderHeight = [self topHeaderHeight];
CGFloat leftHeaderWidth = [self leftHeaderWidth];
CGFloat topheaderHeight = self.topHeaderHeight;
CGFloat leftHeaderWidth = self.leftHeaderWidth;
CGFloat topInsets = 0.f;
if (@available (iOS 11.0, *)) {
@@ -55,46 +71,45 @@ static const CGFloat kColumnMargin = 1;
}
CGFloat contentWidth = 0.0;
NSInteger rowsCount = [self numberOfColumns];
NSInteger rowsCount = self.numberOfColumns;
for (int i = 0; i < rowsCount; i++) {
contentWidth += [self contentWidthForColumn:i];
}
self.leftTableView.frame = CGRectMake(0, topheaderHeight + topInsets, leftHeaderWidth, height - topheaderHeight - topInsets);
self.headerScrollView.frame = CGRectMake(leftHeaderWidth, topInsets, width - leftHeaderWidth, topheaderHeight);
self.headerScrollView.contentSize = CGSizeMake( self.contentTableView.frame.size.width, self.headerScrollView.frame.size.height);
self.contentTableView.frame = CGRectMake(0, 0, contentWidth + [self numberOfColumns] * [self columnMargin] , height - topheaderHeight - topInsets);
self.contentScrollView.frame = CGRectMake(leftHeaderWidth, topheaderHeight + topInsets, width - leftHeaderWidth, height - topheaderHeight - topInsets);
CGFloat contentHeight = height - topheaderHeight - topInsets;
self.leftHeader.frame = CGRectMake(0, topInsets, self.leftHeaderWidth, self.topHeaderHeight);
self.leftTableView.frame = CGRectMake(
0, topheaderHeight + topInsets, leftHeaderWidth, contentHeight
);
self.headerScrollView.frame = CGRectMake(
leftHeaderWidth, topInsets, width - leftHeaderWidth, topheaderHeight
);
self.headerScrollView.contentSize = CGSizeMake(
self.contentTableView.frame.size.width, self.headerScrollView.frame.size.height
);
self.contentTableView.frame = CGRectMake(
0, 0, contentWidth + self.numberOfColumns * self.columnMargin , contentHeight
);
self.contentScrollView.frame = CGRectMake(
leftHeaderWidth, topheaderHeight + topInsets, width - leftHeaderWidth, contentHeight
);
self.contentScrollView.contentSize = self.contentTableView.frame.size;
self.leftHeader.frame = CGRectMake(0, topInsets, [self leftHeaderWidth], [self topHeaderHeight]);
}
- (void)loadUI {
[self loadHeaderScrollView];
[self loadContentScrollView];
[self loadLeftView];
}
- (void)reloadData {
[self loadLeftViewData];
[self loadContentData];
[self loadHeaderData];
}
#pragma mark - UI
- (void)loadHeaderScrollView {
UIScrollView *headerScrollView = [UIScrollView new];
headerScrollView.delegate = self;
self.headerScrollView = headerScrollView;
self.headerScrollView.backgroundColor = [UIColor colorWithWhite:0.803 alpha:0.850];
UIScrollView *headerScrollView = [UIScrollView new];
headerScrollView.delegate = self;
headerScrollView.backgroundColor = FLEXColor.secondaryGroupedBackgroundColor;
self.headerScrollView = headerScrollView;
[self addSubview:headerScrollView];
}
- (void)loadContentScrollView {
UIScrollView *scrollView = [UIScrollView new];
scrollView.bounces = NO;
scrollView.delegate = self;
@@ -103,82 +118,89 @@ static const CGFloat kColumnMargin = 1;
tableView.delegate = self;
tableView.dataSource = self;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[tableView registerClass:[FLEXDBQueryRowCell class]
forCellReuseIdentifier:kFLEXDBQueryRowCellReuse
];
[self addSubview:scrollView];
[scrollView addSubview:tableView];
[self addSubview:scrollView];
self.contentScrollView = scrollView;
self.contentTableView = tableView;
self.contentTableView = tableView;
}
- (void)loadLeftView {
UITableView *leftTableView = [UITableView new];
UITableView *leftTableView = [UITableView new];
leftTableView.delegate = self;
leftTableView.dataSource = self;
leftTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.leftTableView = leftTableView;
[self addSubview:leftTableView];
UIView *leftHeader = [UIView new];
leftHeader.backgroundColor = [UIColor colorWithWhite:0.950 alpha:0.668];
UIView *leftHeader = [UIView new];
leftHeader.backgroundColor = FLEXColor.secondaryBackgroundColor;
self.leftHeader = leftHeader;
[self addSubview:leftHeader];
}
#pragma mark - Data
- (void)reloadData {
[self loadLeftViewData];
[self loadContentData];
[self loadHeaderData];
}
- (void)loadHeaderData {
NSArray<UIView *> *subviews = self.headerScrollView.subviews;
for (UIView *subview in subviews) {
// Remove existing headers, if any
for (UIView *subview in self.headerScrollView.subviews) {
[subview removeFromSuperview];
}
CGFloat x = 0.0;
CGFloat w = 0.0;
for (int i = 0; i < [self numberOfColumns] ; i++) {
w = [self contentWidthForColumn:i] + [self columnMargin];
CGFloat xOffset = 0.0;
for (NSInteger column = 0; column < self.numberOfColumns; column++) {
CGFloat width = [self contentWidthForColumn:column] + self.columnMargin;
FLEXTableColumnHeader *cell = [[FLEXTableColumnHeader alloc] initWithFrame:CGRectMake(x, 0, w, [self topHeaderHeight] - 1)];
cell.label.text = [self columnTitleForColumn:i];
[self.headerScrollView addSubview:cell];
FLEXTableColumnHeader *header = [[FLEXTableColumnHeader alloc]
initWithFrame:CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1)
];
header.titleLabel.text = [self columnTitle:column];
FLEXTableColumnHeaderSortType type = [self.sortStatusDict[[self columnTitleForColumn:i]] integerValue];
[cell changeSortStatusWithType:type];
if (column == self.sortColumn) {
header.sortType = self.sortType;
}
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(contentHeaderTap:)];
[cell addGestureRecognizer:gesture];
cell.userInteractionEnabled = YES;
// Header tap gesture
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(contentHeaderTap:)
];
[header addGestureRecognizer:gesture];
header.userInteractionEnabled = YES;
x = x + w;
[self.headerScrollView addSubview:header];
xOffset += width;
}
}
- (void)contentHeaderTap:(UIGestureRecognizer *)gesture {
FLEXTableColumnHeader *header = (FLEXTableColumnHeader *)gesture.view;
NSString *string = header.label.text;
FLEXTableColumnHeaderSortType currentType = [self.sortStatusDict[string] integerValue];
FLEXTableColumnHeaderSortType newType ;
NSInteger newSortColumn = [self.headerScrollView.subviews indexOfObject:gesture.view];
FLEXTableColumnHeaderSortType newType = FLEXNextTableColumnHeaderSortType(self.sortType);
switch (currentType) {
case FLEXTableColumnHeaderSortTypeNone:
newType = FLEXTableColumnHeaderSortTypeAsc;
break;
case FLEXTableColumnHeaderSortTypeAsc:
newType = FLEXTableColumnHeaderSortTypeDesc;
break;
case FLEXTableColumnHeaderSortTypeDesc:
newType = FLEXTableColumnHeaderSortTypeAsc;
break;
}
// Reset old header
FLEXTableColumnHeader *oldHeader = (id)self.headerScrollView.subviews[self.sortColumn];
oldHeader.sortType = FLEXTableColumnHeaderSortTypeNone;
self.sortStatusDict = @{header.label.text : @(newType)};
[header changeSortStatusWithType:newType];
[self.delegate multiColumnTableView:self didTapHeaderWithText:string sortType:newType];
// Update new header
FLEXTableColumnHeader *newHeader = (id)self.headerScrollView.subviews[newSortColumn];
newHeader.sortType = newType;
// Update self
self.sortColumn = newSortColumn;
self.sortType = newType;
// Notify delegate
[self.delegate multiColumnTableView:self didSelectHeaderForColumn:newSortColumn sortType:newType];
}
- (void)loadContentData {
@@ -189,39 +211,30 @@ static const CGFloat kColumnMargin = 1;
[self.leftTableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UIColor *backgroundColor = UIColor.whiteColor;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Alternating background color
UIColor *backgroundColor = FLEXColor.primaryBackgroundColor;
if (indexPath.row % 2 != 0) {
backgroundColor = [UIColor colorWithWhite:0.950 alpha:0.750];
backgroundColor = FLEXColor.secondaryBackgroundColor;
}
if (tableView != self.leftTableView) {
self.rowData = [self.dataSource contentAtRow:indexPath.row];
FLEXTableContentCell *cell = [FLEXTableContentCell cellWithTableView:tableView
columnNumber:[self numberOfColumns]];
// Left side table view for row numbers
if (tableView == self.leftTableView) {
FLEXTableLeftCell *cell = [FLEXTableLeftCell cellWithTableView:tableView];
cell.contentView.backgroundColor = backgroundColor;
cell.delegate = self;
for (int i = 0 ; i < cell.labels.count; i++) {
UILabel *label = cell.labels[i];
label.textColor = UIColor.blackColor;
NSString *content = [NSString stringWithFormat:@"%@",self.rowData[i]];
if ([content isEqualToString:@"<null>"]) {
label.textColor = UIColor.lightGrayColor;
content = @"NULL";
}
label.text = content;
label.backgroundColor = backgroundColor;
}
cell.titlelabel.text = [self rowTitle:indexPath.row];
return cell;
}
// Right side table view for data
else {
FLEXTableLeftCell *cell = [FLEXTableLeftCell cellWithTableView:tableView];
self.rowData = [self.dataSource contentForRow:indexPath.row];
FLEXDBQueryRowCell *cell = [tableView
dequeueReusableCellWithIdentifier:kFLEXDBQueryRowCellReuse forIndexPath:indexPath
];
cell.contentView.backgroundColor = backgroundColor;
cell.titlelabel.text = [self rowTitleForRow:indexPath.row];
cell.data = [self.dataSource contentForRow:indexPath.row];
NSAssert(cell.data.count == self.numberOfColumns, @"Count of data provided was incorrect");
return cell;
}
}
@@ -230,12 +243,11 @@ static const CGFloat kColumnMargin = 1;
return [self.dataSource numberOfRowsInTableView:self];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self.dataSource multiColumnTableView:self heightForContentCellInRow:indexPath.row];
}
// Scroll all scroll views in sync
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.contentScrollView) {
self.headerScrollView.contentOffset = scrollView.contentOffset;
@@ -251,23 +263,23 @@ static const CGFloat kColumnMargin = 1;
}
}
#pragma mark -
#pragma mark UITableView Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == self.leftTableView) {
[self.contentTableView selectRowAtIndexPath:indexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
[self.contentTableView
selectRowAtIndexPath:indexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone
];
}
else if (tableView == self.contentTableView) {
[self.leftTableView selectRowAtIndexPath:indexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
[self.delegate multiColumnTableView:self didSelectRow:indexPath.row];
}
}
#pragma mark -
#pragma mark DataSource Accessor
- (NSInteger)numberOfRows {
@@ -278,16 +290,12 @@ static const CGFloat kColumnMargin = 1;
return [self.dataSource numberOfColumnsInTableView:self];
}
- (NSString *)columnTitleForColumn:(NSInteger)column {
return [self.dataSource columnNameInColumn:column];
- (NSString *)columnTitle:(NSInteger)column {
return [self.dataSource columnTitle:column];
}
- (NSString *)rowTitleForRow:(NSInteger)row {
return [self.dataSource rowNameInRow:row];
}
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row; {
return [self.dataSource contentAtColumn:column row:row];
- (NSString *)rowTitle:(NSInteger)row {
return [self.dataSource rowTitle:row];
}
- (CGFloat)contentWidthForColumn:(NSInteger)column {
@@ -310,9 +318,4 @@ static const CGFloat kColumnMargin = 1;
return kColumnMargin;
}
- (void)tableContentCell:(FLEXTableContentCell *)tableView labelDidTapWithText:(NSString *)text {
[self.delegate multiColumnTableView:self didTapLabelWithText:text];
}
@end
@@ -7,6 +7,8 @@
//
#import "FLEXRealmDatabaseManager.h"
#import "NSArray+FLEX.h"
#import "FLEXSQLResult.h"
#if __has_include(<Realm/Realm.h>)
#import <Realm/Realm.h>
@@ -18,92 +20,83 @@
@interface FLEXRealmDatabaseManager ()
@property (nonatomic, copy) NSString *path;
@property (nonatomic) RLMRealm * realm;
@property (nonatomic) RLMRealm *realm;
@end
//#endif
@implementation FLEXRealmDatabaseManager
static Class RLMRealmClass = nil;
- (instancetype)initWithPath:(NSString*)aPath {
Class realmClass = NSClassFromString(@"RLMRealm");
if (realmClass == nil) {
+ (void)load {
RLMRealmClass = NSClassFromString(@"RLMRealm");
}
+ (instancetype)managerForDatabase:(NSString *)path {
return [[self alloc] initWithPath:path];
}
- (instancetype)initWithPath:(NSString *)path {
if (!RLMRealmClass) {
return nil;
}
self = [super init];
if (self) {
_path = aPath;
_path = path;
if (![self open]) {
return nil;
}
}
return self;
}
- (BOOL)open {
Class realmClass = NSClassFromString(@"RLMRealm");
Class configurationClass = NSClassFromString(@"RLMRealmConfiguration");
if (realmClass == nil || configurationClass == nil) {
if (!RLMRealmClass || !configurationClass) {
return NO;
}
NSError *error = nil;
id configuration = [configurationClass new];
[(RLMRealmConfiguration *)configuration setFileURL:[NSURL fileURLWithPath:self.path]];
self.realm = [realmClass realmWithConfiguration:configuration error:&error];
self.realm = [RLMRealmClass realmWithConfiguration:configuration error:&error];
return (error == nil);
}
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables {
NSMutableArray<NSDictionary<NSString *, id> *> *allTables = [NSMutableArray array];
RLMSchema *schema = [self.realm schema];
for (RLMObjectSchema *objectSchema in schema.objectSchema) {
if (objectSchema.className == nil) {
continue;
}
NSDictionary<NSString *, id> *dictionary = @{@"name":objectSchema.className};
[allTables addObject:dictionary];
}
return allTables;
- (NSArray<NSString *> *)queryAllTables {
// Map each schema to its name
NSArray<NSString *> *tableNames = [self.realm.schema.objectSchema flex_mapped:^id(RLMObjectSchema *schema, NSUInteger idx) {
return schema.className ?: nil;
}];
return [tableNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName {
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
if (objectSchema == nil) {
return nil;
}
NSMutableArray<NSString *> *columnNames = [NSMutableArray array];
for (RLMProperty *property in objectSchema.properties) {
[columnNames addObject:property.name];
}
return columnNames;
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
RLMObjectSchema *objectSchema = [self.realm.schema schemaForClassName:tableName];
// Map each column to its name
return [objectSchema.properties flex_mapped:^id(RLMProperty *property, NSUInteger idx) {
return property.name;
}];
}
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName {
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
RLMObjectSchema *objectSchema = [self.realm.schema schemaForClassName:tableName];
RLMResults *results = [self.realm allObjects:tableName];
if (results.count == 0 || objectSchema == nil) {
if (results.count == 0 || !objectSchema) {
return nil;
}
NSMutableArray<NSDictionary<NSString *, id> *> *allDataEntries = [NSMutableArray array];
for (RLMObject *result in results) {
NSMutableDictionary<NSString *, id> *entry = [NSMutableDictionary dictionary];
for (RLMProperty *property in objectSchema.properties) {
id value = [result valueForKey:property.name];
entry[property.name] = (value) ? (value) : [NSNull null];
}
[allDataEntries addObject:entry];
}
return allDataEntries;
// Map results to an array of rows
return [NSArray flex_mapped:results block:^id(RLMObject *result, NSUInteger idx) {
// Map each row to an array of the values of its properties
return [objectSchema.properties flex_mapped:^id(RLMProperty *property, NSUInteger idx) {
return [result valueForKey:property.name] ?: NSNull.null;
}];
}];
}
@end
@@ -0,0 +1,48 @@
//
// FLEXSQLResult.h
// FLEX
//
// Created by Tanner on 3/3/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FLEXSQLResult : NSObject
/// Describes the result of a non-select query, or an error of any kind of query
+ (instancetype)message:(NSString *)message;
/// Describes the result of a known failed execution
+ (instancetype)error:(NSString *)message;
/// @param rowData A list of rows, where each element in the row
/// corresponds to the column given in /c columnNames
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData;
@property (nonatomic, readonly, nullable) NSString *message;
/// A value of YES means this is surely an error,
/// but it still might be an error even with a value of NO
@property (nonatomic, readonly) BOOL isError;
/// A list of column names
@property (nonatomic, readonly, nullable) NSArray<NSString *> *columns;
/// A list of rows, where each element in the row corresponds
/// to the value of the column at the same index in \c columns.
///
/// That is, given a row, looping over the contents of the row and
/// the contents of \c columns will give you key-value pairs of
/// column names to column values for that row.
@property (nonatomic, readonly, nullable) NSArray<NSArray<NSString *> *> *rows;
/// A list of rows where the fields are paired to column names.
///
/// This property is lazily constructed by looping over
/// the rows and columns present in the other two properties.
@property (nonatomic, readonly, nullable) NSArray<NSDictionary<NSString *, id> *> *keyedRows;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,53 @@
//
// FLEXSQLResult.m
// FLEX
//
// Created by Tanner on 3/3/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXSQLResult.h"
#import "NSArray+FLEX.h"
@implementation FLEXSQLResult
@synthesize keyedRows = _keyedRows;
+ (instancetype)message:(NSString *)message {
return [[self alloc] initWithmessage:message columns:nil rows:nil];
}
+ (instancetype)error:(NSString *)message {
FLEXSQLResult *result = [self message:message];
result->_isError = YES;
return result;
}
+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
return [[self alloc] initWithmessage:nil columns:columnNames rows:rowData];
}
- (id)initWithmessage:(NSString *)message columns:(NSArray *)columns rows:(NSArray<NSArray *> *)rows {
NSParameterAssert(message || (columns && rows));
NSParameterAssert(columns.count == rows.firstObject.count);
self = [super init];
if (self) {
_message = message;
_columns = columns;
_rows = rows;
}
return self;
}
- (NSArray<NSDictionary<NSString *,id> *> *)keyedRows {
if (!_keyedRows) {
_keyedRows = [self.rows flex_mapped:^id(NSArray<NSString *> *row, NSUInteger idx) {
return [NSDictionary dictionaryWithObjects:row forKeys:self.columns];
}];
}
return _keyedRows;
}
@end
@@ -13,7 +13,20 @@
#import <Foundation/Foundation.h>
#import "FLEXDatabaseManager.h"
#import "FLEXSQLResult.h"
@interface FLEXSQLiteDatabaseManager : NSObject <FLEXDatabaseManager>
/// Contains the result of the last operation, which may be an error
@property (nonatomic, readonly) FLEXSQLResult *lastResult;
/// Calls into \c sqlite3_last_insert_rowid()
@property (nonatomic, readonly) NSInteger lastRowID;
/// Given a statement like 'SELECT * from @table where @col = @val' and arguments
/// like { @"table": @"Album", @"col": @"year", @"val" @1 } this method will
/// invoke the statement and properly bind the given arguments to the statement.
///
/// You may pass NSStrings, NSData, NSNumbers, or NSNulls as values.
- (FLEXSQLResult *)executeStatement:(NSString *)statement arguments:(NSDictionary<NSString *, id> *)args;
@end
@@ -8,60 +8,71 @@
#import "FLEXSQLiteDatabaseManager.h"
#import "FLEXManager.h"
#import "NSArray+FLEX.h"
#import "FLEXRuntimeConstants.h"
#import <sqlite3.h>
static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
@interface FLEXSQLiteDatabaseManager ()
@property (nonatomic) sqlite3 *db;
@property (nonatomic, copy) NSString *path;
@end
@implementation FLEXSQLiteDatabaseManager {
sqlite3* _db;
NSString* _databasePath;
@implementation FLEXSQLiteDatabaseManager
#pragma mark - FLEXDatabaseManager
+ (instancetype)managerForDatabase:(NSString *)path {
return [[self alloc] initWithPath:path];
}
- (instancetype)initWithPath:(NSString*)aPath {
- (instancetype)initWithPath:(NSString *)path {
self = [super init];
if (self) {
_databasePath = [aPath copy];
self.path = path;;
}
return self;
}
- (void)dealloc {
[self close];
}
- (BOOL)open {
if (_db) {
if (self.db) {
return YES;
}
int err = sqlite3_open(_databasePath.UTF8String, &_db);
int err = sqlite3_open(self.path.UTF8String, &_db);
#if SQLITE_HAS_CODEC
NSString *defaultSqliteDatabasePassword = [FLEXManager sharedManager].defaultSqliteDatabasePassword;
NSString *defaultSqliteDatabasePassword = FLEXManager.sharedManager.defaultSqliteDatabasePassword;
if (defaultSqliteDatabasePassword) {
const char *key = defaultSqliteDatabasePassword.UTF8String;
sqlite3_key(_db, key, (int)strlen(key));
}
#endif
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
if (err != SQLITE_OK) {
return [self storeErrorForLastTask:@"Open"];
}
return YES;
}
- (BOOL)close {
if (!_db) {
if (!self.db) {
return YES;
}
int rc;
BOOL retry;
BOOL triedFinalizingOpenStatements = NO;
BOOL retry, triedFinalizingOpenStatements = NO;
do {
retry = NO;
rc = sqlite3_close(_db);
retry = NO;
rc = sqlite3_close(_db);
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
if (!triedFinalizingOpenStatements) {
triedFinalizingOpenStatements = YES;
@@ -72,126 +83,223 @@ static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master W
retry = YES;
}
}
} else if (SQLITE_OK != rc) {
[self storeErrorForLastTask:@"Close"];
self.db = nil;
return NO;
}
else if (SQLITE_OK != rc) {
NSLog(@"error closing!: %d", rc);
}
}
while (retry);
} while (retry);
_db = nil;
self.db = nil;
return YES;
}
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables {
return [self executeQuery:QUERY_TABLENAMES_SQL];
- (NSInteger)lastRowID {
return (NSInteger)sqlite3_last_insert_rowid(self.db);
}
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName {
- (NSArray<NSString *> *)queryAllTables {
return [[self executeStatement:QUERY_TABLENAMES].rows flex_mapped:^id(NSArray *table, NSUInteger idx) {
return table.firstObject;
}] ?: @[];
}
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
NSArray<NSDictionary<NSString *, id> *> *resultArray = [self executeQuery:sql];
NSMutableArray<NSString *> *array = [NSMutableArray array];
for (NSDictionary<NSString *, id> *dict in resultArray) {
NSString *columnName = (NSString *)dict[@"name"] ?: @"";
[array addObject:columnName];
}
return array;
FLEXSQLResult *results = [self executeStatement:sql];
return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
return column[@"name"];
}] ?: @[];
}
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName {
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@",tableName];
return [self executeQuery:sql];
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
return [self executeStatement:[@"SELECT * FROM "
stringByAppendingString:tableName
]].rows ?: @[];
}
#pragma mark -
#pragma mark - Private
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
return [self executeStatement:sql arguments:nil];
}
- (NSArray<NSDictionary<NSString *, id> *> *)executeQuery:(NSString *)sql {
- (FLEXSQLResult *)executeStatement:(NSString *)sql arguments:(NSDictionary *)args {
[self open];
NSMutableArray<NSDictionary<NSString *, id> *> *resultArray = [NSMutableArray array];
FLEXSQLResult *result = nil;
sqlite3_stmt *pstmt;
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0) == SQLITE_OK) {
while (sqlite3_step(pstmt) == SQLITE_ROW) {
NSUInteger num_cols = (NSUInteger)sqlite3_data_count(pstmt);
if (num_cols > 0) {
NSMutableDictionary<NSString *, id> *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
int columnCount = sqlite3_column_count(pstmt);
int columnIdx = 0;
for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name(pstmt, columnIdx)];
id objectValue = [self objectForColumnIndex:columnIdx stmt:pstmt];
[dict setObject:objectValue forKey:columnName];
}
[resultArray addObject:dict];
int status;
if ((status = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0)) == SQLITE_OK) {
NSMutableArray<NSArray *> *rows = [NSMutableArray new];
// Bind parameters, if any
if (![self bindParameters:args toStatement:pstmt]) {
return self.lastResult;
}
// Grab columns
int columnCount = sqlite3_column_count(pstmt);
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
return @(sqlite3_column_name(pstmt, (int)i));
}];
// Execute statement
while ((status = sqlite3_step(pstmt)) == SQLITE_ROW) {
// Grab rows if this is a selection query
int dataCount = sqlite3_data_count(pstmt);
if (dataCount > 0) {
[rows addObject:[NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
return [self objectForColumnIndex:(int)i stmt:pstmt];
}]];
}
}
if (status == SQLITE_DONE) {
if (rows.count) {
// We selected some rows
result = _lastResult = [FLEXSQLResult columns:columns rows:rows];
} else {
// We executed a query like INSERT, UDPATE, or DELETE
int rowsAffected = sqlite3_changes(_db);
NSString *message = [NSString stringWithFormat:@"%d row(s) affected", rowsAffected];
result = _lastResult = [FLEXSQLResult message:message];
}
} else {
// An error occured executing the query
result = _lastResult = [self errorResult:@"Execution"];
}
} else {
// An error occurred creating the prepared statement
result = _lastResult = [self errorResult:@"Prepared statement"];
}
[self close];
return resultArray;
sqlite3_finalize(pstmt);
return result;
}
#pragma mark - Private
/// @return YES on success, NO if an error was encountered and stored in \c lastResult
- (BOOL)bindParameters:(NSDictionary *)args toStatement:(sqlite3_stmt *)pstmt {
for (NSString *param in args.allKeys) {
int status = SQLITE_OK, idx = sqlite3_bind_parameter_index(pstmt, param.UTF8String);
id value = args[param];
if (idx == 0) {
// No parameter matching that arg
@throw NSInternalInconsistencyException;
}
// Null
if ([value isKindOfClass:[NSNull class]]) {
status = sqlite3_bind_null(pstmt, idx);
}
// String params
else if ([value isKindOfClass:[NSString class]]) {
const char *str = [value UTF8String];
status = sqlite3_bind_text(pstmt, idx, str, (int)strlen(str), SQLITE_TRANSIENT);
}
// Data params
else if ([value isKindOfClass:[NSData class]]) {
const void *blob = [value bytes];
status = sqlite3_bind_blob64(pstmt, idx, blob, [value length], SQLITE_TRANSIENT);
}
// Primitive params
else if ([value isKindOfClass:[NSNumber class]]) {
FLEXTypeEncoding type = [value objCType][0];
switch (type) {
case FLEXTypeEncodingCBool:
case FLEXTypeEncodingChar:
case FLEXTypeEncodingUnsignedChar:
case FLEXTypeEncodingShort:
case FLEXTypeEncodingUnsignedShort:
case FLEXTypeEncodingInt:
case FLEXTypeEncodingUnsignedInt:
case FLEXTypeEncodingLong:
case FLEXTypeEncodingUnsignedLong:
case FLEXTypeEncodingLongLong:
case FLEXTypeEncodingUnsignedLongLong:
status = sqlite3_bind_int64(pstmt, idx, (sqlite3_int64)[value longValue]);
break;
case FLEXTypeEncodingFloat:
case FLEXTypeEncodingDouble:
status = sqlite3_bind_double(pstmt, idx, [value doubleValue]);
break;
default:
@throw NSInternalInconsistencyException;
break;
}
}
// Unsupported type
else {
@throw NSInternalInconsistencyException;
}
if (status != SQLITE_OK) {
return [self storeErrorForLastTask:
[NSString stringWithFormat:@"Binding param named '%@'", param]
];
}
}
return YES;
}
- (BOOL)storeErrorForLastTask:(NSString *)action {
_lastResult = [self errorResult:action];
return NO;
}
- (FLEXSQLResult *)errorResult:(NSString *)description {
const char *error = sqlite3_errmsg(_db);
NSString *message = error ? @(error) : [NSString
stringWithFormat:@"(%@: empty error", description
];
return [FLEXSQLResult error:message];
}
- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
int columnType = sqlite3_column_type(stmt, columnIdx);
id returnValue = nil;
if (columnType == SQLITE_INTEGER) {
returnValue = [NSNumber numberWithLongLong:sqlite3_column_int64(stmt, columnIdx)];
switch (columnType) {
case SQLITE_INTEGER:
return @(sqlite3_column_int64(stmt, columnIdx)).stringValue;
case SQLITE_FLOAT:
return @(sqlite3_column_double(stmt, columnIdx)).stringValue;
case SQLITE_BLOB:
return [NSString stringWithFormat:@"Data (%@ bytes)",
@([self dataForColumnIndex:columnIdx stmt:stmt].length)
];
default:
// Default to a string for everything else
return [self stringForColumnIndex:columnIdx stmt:stmt] ?: NSNull.null;
}
else if (columnType == SQLITE_FLOAT) {
returnValue = [NSNumber numberWithDouble:sqlite3_column_double(stmt, columnIdx)];
}
else if (columnType == SQLITE_BLOB) {
returnValue = [self dataForColumnIndex:columnIdx stmt:stmt];
}
else {
//default to a string for everything else
returnValue = [self stringForColumnIndex:columnIdx stmt:stmt];
}
if (returnValue == nil) {
returnValue = [NSNull null];
}
return returnValue;
}
- (NSString *)stringForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || columnIdx < 0) {
return nil;
}
const char *text = (const char *)sqlite3_column_text(stmt, columnIdx);
return text ? @(text) : nil;
}
- (NSData *)dataForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
return nil;
}
const char *c = (const char *)sqlite3_column_text(stmt, columnIdx);
const void *blob = sqlite3_column_blob(stmt, columnIdx);
NSInteger size = (NSInteger)sqlite3_column_bytes(stmt, columnIdx);
if (!c) {
// null row.
return nil;
}
return [NSString stringWithUTF8String:c];
return blob ? [NSData dataWithBytes:blob length:size] : nil;
}
- (NSData *)dataForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt{
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
return nil;
}
const char *dataBuffer = sqlite3_column_blob(stmt, columnIdx);
int dataSize = sqlite3_column_bytes(stmt, columnIdx);
if (dataBuffer == NULL) {
return nil;
}
return [NSData dataWithBytes:(const void *)dataBuffer length:(NSUInteger)dataSize];
}
@end
@@ -14,11 +14,25 @@ typedef NS_ENUM(NSUInteger, FLEXTableColumnHeaderSortType) {
FLEXTableColumnHeaderSortTypeDesc,
};
NS_INLINE FLEXTableColumnHeaderSortType FLEXNextTableColumnHeaderSortType(
FLEXTableColumnHeaderSortType current) {
switch (current) {
case FLEXTableColumnHeaderSortTypeAsc:
return FLEXTableColumnHeaderSortTypeDesc;
case FLEXTableColumnHeaderSortTypeNone:
case FLEXTableColumnHeaderSortTypeDesc:
return FLEXTableColumnHeaderSortTypeAsc;
}
return FLEXTableColumnHeaderSortTypeNone;
}
@interface FLEXTableColumnHeader : UIView
@property (nonatomic) UILabel *label;
@property (nonatomic) NSInteger index;
@property (nonatomic, readonly) UILabel *titleLabel;
- (void)changeSortStatusWithType:(FLEXTableColumnHeaderSortType)type;
@property (nonatomic) FLEXTableColumnHeaderSortType sortType;
@end
@@ -7,36 +7,41 @@
//
#import "FLEXTableColumnHeader.h"
#import "FLEXColor.h"
#import "UIFont+FLEX.h"
#import "FLEXUtility.h"
@implementation FLEXTableColumnHeader {
UILabel *_arrowLabel;
}
@interface FLEXTableColumnHeader ()
@property (nonatomic, readonly) UILabel *arrowLabel;
@property (nonatomic, readonly) UIView *lineView;
@end
@implementation FLEXTableColumnHeader
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = UIColor.whiteColor;
self.backgroundColor = FLEXColor.secondaryBackgroundColor;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, frame.size.width - 25, frame.size.height)];
label.font = [UIFont systemFontOfSize:13.0];
[self addSubview:label];
self.label = label;
_titleLabel = [UILabel new];
_titleLabel.font = UIFont.flex_defaultTableCellFont;
[self addSubview:_titleLabel];
_arrowLabel = [[UILabel alloc] initWithFrame:CGRectMake(frame.size.width - 20, 0, 20, frame.size.height)];
_arrowLabel.font = [UIFont systemFontOfSize:13.0];
_arrowLabel = [UILabel new];
_arrowLabel.font = UIFont.flex_defaultTableCellFont;
[self addSubview:_arrowLabel];
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(frame.size.width - 1, 2, 1, frame.size.height - 4)];
line.backgroundColor = [UIColor colorWithWhite:0.803 alpha:0.850];
[self addSubview:line];
_lineView = [UIView new];
_lineView.backgroundColor = FLEXColor.hairlineColor;
[self addSubview:_lineView];
}
return self;
}
- (void)changeSortStatusWithType:(FLEXTableColumnHeaderSortType)type {
- (void)setSortType:(FLEXTableColumnHeaderSortType)type {
_sortType = type;
switch (type) {
case FLEXTableColumnHeaderSortTypeNone:
_arrowLabel.text = @"";
@@ -50,8 +55,14 @@
}
}
- (void)layoutSubviews {
[super layoutSubviews];
CGSize size = self.frame.size;
self.titleLabel.frame = CGRectMake(5, 0, size.width - 25, size.height);
self.arrowLabel.frame = CGRectMake(size.width - 20, 0, 20, size.height);
self.lineView.frame = CGRectMake(size.width - 1, 2, FLEXPointsToPixels(1), size.height - 4);
}
@end
@@ -1,27 +0,0 @@
//
// FLEXTableContentCell.h
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FLEXTableContentCell;
@protocol FLEXTableContentCellDelegate <NSObject>
@optional
- (void)tableContentCell:(FLEXTableContentCell *)tableView labelDidTapWithText:(NSString *)text;
@end
@interface FLEXTableContentCell : UITableViewCell
@property (nonatomic) NSArray<UILabel *> *labels;
@property (nonatomic, weak) id<FLEXTableContentCellDelegate> delegate;
+ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number;
@end
@@ -1,63 +0,0 @@
//
// FLEXTableContentCell.m
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
//
#import "FLEXTableContentCell.h"
#import "FLEXMultiColumnTableView.h"
@interface FLEXTableContentCell ()
@end
@implementation FLEXTableContentCell
+ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number; {
static NSString *identifier = @"FLEXTableContentCell";
FLEXTableContentCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[FLEXTableContentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
NSMutableArray<UILabel *> *labels = [NSMutableArray array];
for (int i = 0; i < number ; i++) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = UIColor.whiteColor;
label.font = [UIFont systemFontOfSize:13.0];
label.textAlignment = NSTextAlignmentLeft;
label.backgroundColor = UIColor.greenColor;
[labels addObject:label];
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:cell
action:@selector(labelDidTap:)];
[label addGestureRecognizer:gesture];
label.userInteractionEnabled = YES;
[cell.contentView addSubview:label];
cell.contentView.backgroundColor = UIColor.whiteColor;
}
cell.labels = labels;
}
return cell;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat labelWidth = self.contentView.frame.size.width / self.labels.count;
CGFloat labelHeight = self.contentView.frame.size.height;
for (int i = 0; i < self.labels.count; i++) {
UILabel *label = self.labels[i];
label.frame = CGRectMake(labelWidth * i + 5, 0, (labelWidth - 10), labelHeight);
}
}
- (void)labelDidTap:(UIGestureRecognizer *)gesture {
UILabel *label = (UILabel *)gesture.view;
if ([self.delegate respondsToSelector:@selector(tableContentCell:labelDidTapWithText:)]) {
[self.delegate tableContentCell:self labelDidTapWithText:label.text];
}
}
@end
@@ -10,7 +10,7 @@
@interface FLEXTableContentViewController : UIViewController
@property (nonatomic) NSArray<NSString *> *columnsArray;
@property (nonatomic) NSArray<NSDictionary<NSString *, id> *> *contentsArray;
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData;
@end
@@ -9,81 +9,73 @@
#import "FLEXTableContentViewController.h"
#import "FLEXMultiColumnTableView.h"
#import "FLEXWebViewController.h"
#import "FLEXUtility.h"
@interface FLEXTableContentViewController ()<FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate>
@interface FLEXTableContentViewController () <
FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate
>
@property (nonatomic, readonly) NSArray<NSString *> *columns;
@property (nonatomic, copy) NSArray<NSArray *> *rows;
@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
@end
@implementation FLEXTableContentViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData {
FLEXTableContentViewController *controller = [self new];
controller->_columns = columnNames;
controller->_rows = rowData;
return controller;
}
- (void)loadView {
[super loadView];
[self.view addSubview:self.multiColumnView];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
[self.multiColumnView reloadData];
}
#pragma mark -
#pragma mark init SubView
- (FLEXMultiColumnTableView *)multiColumnView {
if (!_multiColumnView) {
_multiColumnView = [[FLEXMultiColumnTableView alloc] initWithFrame:
CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
_multiColumnView = [[FLEXMultiColumnTableView alloc]
initWithFrame:FLEXRectSetSize(CGRectZero, self.view.frame.size)
];
_multiColumnView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
_multiColumnView.backgroundColor = UIColor.whiteColor;
_multiColumnView.dataSource = self;
_multiColumnView.delegate = self;
_multiColumnView.dataSource = self;
_multiColumnView.delegate = self;
}
return _multiColumnView;
}
#pragma mark MultiColumnTableView DataSource
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView {
return self.columnsArray.count;
return self.columns.count;
}
- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView {
return self.contentsArray.count;
return self.rows.count;
}
- (NSString *)columnNameInColumn:(NSInteger)column {
return self.columnsArray[column];
- (NSString *)columnTitle:(NSInteger)column {
return self.columns[column];
}
- (NSString *)rowNameInRow:(NSInteger)row {
return [NSString stringWithFormat:@"%ld",(long)row];
- (NSString *)rowTitle:(NSInteger)row {
return @(row).stringValue;
}
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row {
if (self.contentsArray.count > row) {
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
if (self.contentsArray.count > column) {
return [NSString stringWithFormat:@"%@",[dic objectForKey:self.columnsArray[column]]];
}
}
return @"";
}
- (NSArray *)contentAtRow:(NSInteger)row {
NSMutableArray *result = [NSMutableArray array];
if (self.contentsArray.count > row) {
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
for (int i = 0; i < self.columnsArray.count; i ++) {
[result addObject:dic[self.columnsArray[i]]];
}
return result;
}
return nil;
- (NSArray *)contentForRow:(NSInteger)row {
return self.rows[row];
}
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
@@ -101,48 +93,62 @@
}
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView {
NSString *str = [NSString stringWithFormat:@"%lu",(unsigned long)self.contentsArray.count];
NSDictionary<NSString *, id> *attrs = @{@"NSFontAttributeName":[UIFont systemFontOfSize:17.0]};
CGSize size = [str boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attrs context:nil].size;
NSString *str = [NSString stringWithFormat:@"%lu",(unsigned long)self.rows.count];
NSDictionary *attrs = @{ NSFontAttributeName : [UIFont systemFontOfSize:17.0] };
CGSize size = [str boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attrs context:nil
].size;
return size.width + 20;
}
#pragma mark -
#pragma mark MultiColumnTableView Delegate
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapLabelWithText:(NSString *)text {
FLEXWebViewController * detailViewController = [[FLEXWebViewController alloc] initWithText:text];
[self.navigationController pushViewController:detailViewController animated:YES];
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didSelectRow:(NSInteger)row {
NSArray<NSString *> *fields = [self.rows[row] flex_mapped:^id(NSString *field, NSUInteger idx) {
return [NSString stringWithFormat:@"%@:\n%@", self.columns[idx], field];
}];
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title([@"Row " stringByAppendingString:@(row).stringValue]);
NSString *message = [fields componentsJoinedByString:@"\n\n"];
make.message(message);
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = message;
});
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
}
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType {
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
didSelectHeaderForColumn:(NSInteger)column
sortType:(FLEXTableColumnHeaderSortType)sortType {
NSArray<NSDictionary<NSString *, id> *> *sortContentData = [self.contentsArray sortedArrayUsingComparator:^NSComparisonResult(NSDictionary<NSString *, id> * obj1, NSDictionary<NSString *, id> * obj2) {
if ([obj1 objectForKey:text] == [NSNull null]) {
return NSOrderedAscending;
}
if ([obj2 objectForKey:text] == [NSNull null]) {
return NSOrderedDescending;
}
if (![[obj1 objectForKey:text] respondsToSelector:@selector(compare:)] && ![[obj2 objectForKey:text] respondsToSelector:@selector(compare:)]) {
NSArray<NSArray *> *sortContentData = [self.rows
sortedArrayUsingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
id a = obj1[column], b = obj2[column];
if (a == NSNull.null) {
return NSOrderedAscending;
}
if (b == NSNull.null) {
return NSOrderedDescending;
}
if ([a respondsToSelector:@selector(compare:)] && [b respondsToSelector:@selector(compare:)]) {
return [a compare:b];
}
return NSOrderedSame;
}
NSComparisonResult result = [[obj1 objectForKey:text] compare:[obj2 objectForKey:text]];
return result;
}];
];
if (sortType == FLEXTableColumnHeaderSortTypeDesc) {
NSEnumerator *contentReverseEnumerator = sortContentData.reverseObjectEnumerator;
sortContentData = [NSArray arrayWithArray:contentReverseEnumerator.allObjects];
sortContentData = sortContentData.reverseObjectEnumerator.allObjects.copy;
}
self.contentsArray = sortContentData;
self.rows = sortContentData;
[self.multiColumnView reloadData];
}
@@ -151,19 +157,18 @@
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
[super willTransitionToTraitCollection:newCollection
withTransitionCoordinator:coordinator];
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
self->_multiColumnView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
self.multiColumnView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
}
else {
self->_multiColumnView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
self.multiColumnView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
}
[self.view setNeedsLayout];
} completion:nil];
}
@end
@@ -16,13 +16,13 @@
if (!cell) {
cell = [[FLEXTableLeftCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectZero];
UILabel *textLabel = [UILabel new];
textLabel.textAlignment = NSTextAlignmentCenter;
textLabel.font = [UIFont systemFontOfSize:13.0];
textLabel.backgroundColor = UIColor.clearColor;
[cell.contentView addSubview:textLabel];
cell.titlelabel = textLabel;
}
return cell;
}
@@ -6,9 +6,9 @@
// Copyright © 2015年 Peng Tao. All rights reserved.
//
#import "FLEXTableViewController.h"
#import "FLEXFilteringTableViewController.h"
@interface FLEXTableListViewController : FLEXTableViewController
@interface FLEXTableListViewController : FLEXFilteringTableViewController
+ (BOOL)supportsExtension:(NSString *)extension;
- (instancetype)initWithPath:(NSString *)path;

Some files were not shown because too many files have changed in this diff Show More