uikit0.1.2UIKit library for clojure-objc dependencies
| (this space intentionally left almost blank) | |||
(ns uikit.core) | ||||
(defn- nsdictionary [map] (when map (let [d ($ ($ NSMutableDictionary) :new)] (doseq [[k v] map] ($ d :setObject v :forKey (name k))) d))) | ||||
(def default-center ($ ($ NSNotificationCenter) :defaultCenter)) | ||||
Adds an observer for a notification. The handler is retained. | (defn add-observer ([target handler n] (add-observer target handler "invokeWithId:" n)) ([target handler selector n] ($ default-center :addObserver ($ handler :retain) :selector (sel selector) :name (name n) :object target))) | |||
Removes an observer from the default notification center. Releases the handler | (defn remove-observer [handler] ($ default-center :removeObserver handler) ($ handler :autorelease)) | |||
Post a notification to an object | (defn post-notification ([target n] (post-notification target n nil)) ([target n info] ($ default-center :postNotificationName (name n) :object target :userInfo (nsdictionary info)))) | |||
(def constraint-regex #"C:(\w*)\.(\w*)(=|<=|>=)(\w*)\.(\w*) ?(-?\w*\.?\w*) ?(-?\w*\.?\w*)") | ||||
(defn map-invert [m] (reduce (fn [m [k v]] (assoc m v k)) {} m)) | ||||
(def layout-constraints {:<= -1 := 0 :>= 1 :left 1 :right 2 :top 3 :bottom 4 :leading 5 :trailing 6 :width 7 :height 8 :centerx 9 :centery 10 :baseline 11 :nil 0}) | ||||
(def rlayout-constraints (map-invert layout-constraints)) | ||||
(defn not-found [c] (throw (Exception. (str "Constraint not found " c)))) | ||||
(defn resolve-constraint [c] (if (number? c) (if (some #{c} (vals layout-constraints)) c (not-found c)) (if-let [c (layout-constraints (keyword (name c)))] c (not-found c)))) | ||||
(defn parse-constraint [^String c] (if-not (re-find #"^C:" c) c (if-let [[f1 p1 e f2 p2 m c] (next (re-find constraint-regex c))] [(str f1 "-" p1) f1 (resolve-constraint p1) (resolve-constraint e) f2 (resolve-constraint p2) (if-not (empty? m) (read-string m) 1.0) (if-not (empty? c) (read-string c) 0.0)] (throw (Exception. (str "Invalid custom constraint: '" c "'. Use format: C:{name}.[left|right|top|bottom|leading|trailing|width|height|centerx|centery|baseline][=|<=|>=]{name}.[left|right|top|bottom|leading|trailing|width|height|centerx|centery|baseline][=|<=|>=] multiplier? offset?")))))) | ||||
Creates NSLayoutConstraints from the constraints definitions | (defn autolayout [ui views c] (if (string? c) (let [c ($ ($ NSLayoutConstraint) :constraintsWithVisualFormat c :options 0 :metrics 0 :views views)] ($ ui :addConstraints c) c) (let [[_ a b c d e f g] c] (let [c ($ ($ NSLayoutConstraint) :constraintWithItem ($ views :objectForKey a) :attribute b :relatedBy c :toItem (if (= d "nil") nil ($ views :objectForKey d)) :attribute e :multiplier f :constant g)] ($ ui :addConstraint c) c)))) | |||
Sets a property using objc selectors. Accepts multiple args selectors: :setTitle:forState ["Hello" 0] | (defn set-property [view [k v]] (let [[view k] (if-not (vector? k) [view k] (loop [view view [f & other] k] (if-not other [view f] (recur ((sel (name f)) view) other))))] (when-not (#{:parent-constraints :constraints :events :gestures} k) (if (and (vector? v) (or (empty? v) (re-find #":" (name k)))) (apply (partial (sel (name k)) view) v) ((sel (name k)) view v))))) | |||
Creates a scope for a view | (defn create-scope ([] (create-scope {})) ([m] (atom (assoc m :observers (atom []) :state (atom {}) :retains (atom []))))) | |||
(defn assoc-noreplace [a k v] (loop [i 1] (let [kk (keyword (str (name k) (if (= 1 i) i)))] (if (@a kk) (recur (inc i)) (swap! a assoc kk v))))) | ||||
(defn get-children [children] (if (and (= 1 (count children)) (not (vector? (first children)))) (first children) children)) | ||||
(defn collect-constraints [[type tag props & children]] (into (:constraints props) (loop [c [] [f & o] (get-children children)] (if f (recur (if-let [p (:parent-constraints (nth f 2))] (conj c p) c) o) (flatten c))))) | ||||
Instantiates a ui from clojure data. | (defn create-ui ([v] (create-ui (create-scope) v)) ([scope [clazz tag props & children :as node]] (let [view (if (keyword? clazz) ($ (objc-class (symbol (name clazz))) :new) (do ($ clazz :retain) clazz)) views (if-let [views (:views @scope)] views (let [views ($ ($ NSMutableDictionary) :new)] (swap! scope assoc :views views) views))] ($ views :setValue view :forKey (name tag)) (swap! scope assoc tag view) (swap! (:retains @scope) conj view) (doseq [p (if (map? props) props (partition 2 props))] (set-property view p)) (doseq [c (get-children children)] (let [s (create-ui scope c)] ($ s :setTranslatesAutoresizingMaskIntoConstraints false) ($ view :addSubview s))) (let [allc (map parse-constraint (collect-constraints node))] (when-not (empty? allc) (let [rscope (map-invert @scope)] (doseq [c allc] (if (string? c) (let [l (autolayout view views c)] (when-let [cc ($ l :count)] ;; make it safe for the jvm (doseq [n (range cc)] (let [i ($ l :objectAtIndex n) item1 (rscope ($ i :firstItem)) attr1 (rlayout-constraints ($ i :firstAttribute))] (assoc-noreplace scope (str (name item1) "-" (name attr1)) i) (when-let [sec ($ i :secondItem)] (let [item2 (rscope sec) attr2 (rlayout-constraints ($ i :secondAttribute))] (assoc-noreplace scope (str (name item2) "-" (name attr2)) i))))))) (assoc-noreplace scope (first c) (autolayout view views c))))))) (doseq [[k v] (let [g (:gestures props)] (if (map? g) g (partition 2 g)))] (let [handler (if (map? v) (:handler v) v) selector (sel "invoke") handler #(handler @scope) g ($ ($ (objc-class (name k)) :alloc) :initWithTarget handler :action selector)] ($ handler :retain) (swap! (:retains @scope) conj handler) (when (map? v) (doseq [p (dissoc v :handler)] (set-property g p))) ($ view :addGestureRecognizer g))) (doseq [[k v] (let [g (:events props)] (if (map? g) g (partition 2 g)))] (let [handler #(v (assoc @scope :event %)) method "invokeWithId:"] (if (keyword? k) (let [kname (name k)] (swap! (:observers @scope) conj handler) (add-observer view handler method kname)) (do ($ handler :retain) (swap! (:retains @scope) conj handler) ($ view :addTarget handler :action (sel method) :forControlEvents k))))) view))) | |||
Gets the key window | (defn key-window [] (-> ($ UIApplication) ($ :sharedApplication) ($ :keyWindow))) | |||
Gets the top controller | (defn top-controller [] ($ (key-window) :rootViewController)) | |||
Gets the top view | (defn top-view [] ($ (top-controller) :view)) | |||
Setups global observers for UIKeyboardWillShowNotification and UIKeyboardWillHideNotification | (defn setup-keyboard [keyboard-will-show keyboard-will-hide] (add-observer nil keyboard-will-show :UIKeyboardWillShowNotification) (add-observer nil keyboard-will-hide :UIKeyboardWillHideNotification)) | |||
Deallocs everything in a uikit scope | (defn dealloc [scope] (doseq [v @(:retains @scope)] (post-notification v :dealloc) ($ v :release)) (doseq [v @(:observers @scope)] (remove-observer v)) ($ (:views @scope) :release) (reset! scope nil)) | |||
uikit internal controller implementation | (defnstype UIKitController UIViewController ([^:id self :initWith ^:id [view s]] (doto ($$ self :init) ($ :setView ($ view :retain)) (objc-set! :scope s) (#(post-notification ($ % :view) :init)))) ([^:id self :scope] @(objc-get self :scope)) ([self :shouldAutorotate] (:shouldAutorotate (objc-get self :scope))) ([self :viewDidLoad] (post-notification ($ self :view) :viewDidLoad) ($$ self :viewDidLoad)) ([self :didReceiveMemoryWarning] (post-notification ($ self :view) :didReceiveMemoryWarning) ($$ self :didReceiveMemoryWarning)) ([self :viewDidLayoutSubviews] (post-notification ($ self :view) :viewDidLayoutSubviews) ($$ self :viewDidLayoutSubviews)) ([self :viewWillLayoutSubviews] (post-notification ($ self :view) :viewWillLayoutSubviews) ($$ self :viewWillLayoutSubviews)) ([self :viewDidAppear animated] (post-notification ($ self :view) :viewDidAppear) ($$ self :viewDidAppear animated)) ([self :viewDidDisappear animated] (post-notification ($ self :view) :viewDidDisappear) ($$ self :viewDidDisappear animated)) ([self :viewWillAppear animated] (post-notification ($ self :view) :viewWillAppear) ($$ self :viewWillAppear animated)) ([self :viewWillDisappear animated] (post-notification ($ self :view) :viewWillDisappear) ($$ self :viewWillDisappear animated)) ([self :dealloc] (dealloc (objc-get self :scope)) ($ ($ self :view) :release) ($$ self :dealloc))) | |||
Creates a uikit controller with a title and a view data | (defn controller ([title view] (controller title view {})) ([title view init] (let [scope (create-scope init) view (create-ui scope view)] (doto ($ ($ ($ UIKitController) :alloc) :initWith [view scope]) ($ :setTitle title) ($ :setView view) ($ :autorelease))))) | |||
Pushes a controller into the top navigation controller | (defn nav-push ([controller] (nav-push controller false)) ([controller animated] ($ (top-controller) :pushViewController controller :animated animated))) | |||
Pops a controller from the current navigation controller | (defn nav-pop ([] (nav-pop true)) ([animated] ($ (top-controller) :popViewControllerAnimated animated))) | |||
Gets the top controller from the current navigation controller | (defn nav-top-controller ([] (nav-top-controller (top-controller))) ([nav] ($ nav :visibleViewController))) | |||
Creates and shows a simple UIAlertView | (defn alert! ([title msg] (alert! title msg "Cancel" nil)) ([title msg cancel] (alert! title msg cancel nil)) ([title msg cancel delegate] (-> ($ UIAlertView) ($ :alloc) ($ :initWithTitle title :message msg :delegate delegate :cancelButtonTitle cancel :otherButtonTitles nil) ($ :autorelease) ($ :show)))) | |||
Creates a UIButton with a type | (defn button [type] ($ ($ UIButton) :buttonWithType type)) | |||
Mutates ui (ui! scope {:field:setMethod val1 :other:setSomethingElse val2}) | (defmacro ui! [scope pairs] (let [code (for [[k v] pairs] (let [[_ field method] (re-matches #"(\w*):(.*)" (name k))] (when (and field method) (let [kfield (keyword field) view `(~scope ~kfield) kmethod (keyword method)] (if (= :nop v) `((sel ~method) ~view) `(set-property ~view [~kmethod ~v]))))))] `(clojure.lang.RT/dispatchInMainSync (fn [] ~@code)))) | |||