This commit is contained in:
Marino 2016-12-02 11:02:06 +01:00
Коммит b8a008690b
2550 изменённых файлов: 218849 добавлений и 0 удалений

28
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,28 @@
# file
#########################################################################
# #
# Title - .gitignore file #
# For - iOS - Xcode7 #
# Updated on - 01/Dicember/2016 #
# #
#########################################################################
############ Various settings
xcuserdata/
############ CRYPTO CLOUD #################
Crypto Cloud.xcodeproj
iOSClient/CryptoCloud.xcassets
iOSClient/Crypto Cloud.entitlements
iOSClient/LaunchScreen.xib
iOSClient/Networking/DB*.*
iOSClient/InfoPlist/InfoCryptoCloud.plist
Share Ext/Share Ext.entitlements
########### OSX ###########
# OS X temporary files that should never be committed
.DS_Store
.AppleDouble
.LSOverride

1
AUTHORS Executable file
Просмотреть файл

@ -0,0 +1 @@
Marino Faggiana <m.faggiana@twsweb.it>

13
COPYING.iOS Executable file
Просмотреть файл

@ -0,0 +1,13 @@
The Nextcloud iOS developers are aware that the terms of service that
apply to apps distributed via Apple's App Store services may conflict
with rights granted under the Nextcloud iOS license, the GNU General
Public License, version 3 or (at your option) any later version. The
copyright holders of the Nextcloud iOS app do not wish this conflict
to prevent the otherwise-compliant distribution of derived apps via
the App Store. Therefore, we have committed not to pursue any license
violation that results solely from the conflict between the GNU GPLv3
or any later version and the Apple App Store terms of service. In
other words, as long as you comply with the GPL in all other respects,
including its requirements to provide users with source code and the
text of the license, we will not object to your distribution of the
Nextcloud iOS app through the App Store.

15
ChangeLog Executable file
Просмотреть файл

@ -0,0 +1,15 @@
2016–10-31 Marino Faggiana <m.faggiana@twsweb.it>
* Version 2.14 00015 released.
* New features:
* Control if file exists in 'manual' upload. (Marino Faggiana)
* Add main-menu item : Folders on top. (Marino Faggiana)
* Bug fixes:
* Improved (create new class) Synchronization folders. (Marino Faggiana)
* Improved 'Upload all camera photos/videos' bugfix if error block all, automatic default overwrite file if exists. (marino Faggiana)
* Detect Wi-Fi on automatic upload, problem database context. (Marino Faggiana)
* View on Control Center. (Marino Faggiana)

674
LICENSE Executable file
Просмотреть файл

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

Просмотреть файл

@ -0,0 +1,37 @@
//
// AESCrypt.h
// Gurpartap Singh
//
// Created by Gurpartap Singh on 06/05/12.
// Copyright (c) 2012 Gurpartap Singh
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@interface AESCrypt : NSObject
+ (NSString *)encrypt:(NSString *)message password:(NSString *)password;
+ (NSString *)decrypt:(NSString *)base64EncodedString password:(NSString *)password;
@end

Просмотреть файл

@ -0,0 +1,50 @@
//
// AESCrypt.m
// Gurpartap Singh
//
// Created by Gurpartap Singh on 06/05/12.
// Copyright (c) 2012 Gurpartap Singh
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "AESCrypt.h"
#import "NSData+Base64.h"
#import "NSString+Base64.h"
#import "NSData+CommonCrypto.h"
@implementation AESCrypt
+ (NSString *)encrypt:(NSString *)message password:(NSString *)password {
NSData *encryptedData = [[message dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptedDataUsingKey:[[password dataUsingEncoding:NSUTF8StringEncoding] SHA256Hash] error:nil];
NSString *base64EncodedString = [NSString base64StringFromData:encryptedData length:[encryptedData length]];
return base64EncodedString;
}
+ (NSString *)decrypt:(NSString *)base64EncodedString password:(NSString *)password {
NSData *encryptedData = [NSData base64DataFromString:base64EncodedString];
NSData *decryptedData = [encryptedData decryptedAES256DataUsingKey:[[password dataUsingEncoding:NSUTF8StringEncoding] SHA256Hash] error:nil];
return [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
}
@end

Просмотреть файл

@ -0,0 +1,17 @@
//
// NSData+Base64.m
// Gurpartap Singh
//
// Created by Gurpartap Singh on 06/05/12.
// Copyright (c) 2012 Gurpartap Singh. All rights reserved.
//
#import <Foundation/Foundation.h>
@class NSString;
@interface NSData (Base64Additions)
+ (NSData *)base64DataFromString:(NSString *)string;
@end

Просмотреть файл

@ -0,0 +1,110 @@
//
// NSData+Base64.h
// Gurpartap Singh
//
// Created by Gurpartap Singh on 06/05/12.
// Copyright (c) 2012 Gurpartap Singh. All rights reserved.
//
#import "NSData+Base64.h"
@implementation NSData (Base64Additions)
+ (NSData *)base64DataFromString:(NSString *)string {
unsigned long ixtext, lentext;
unsigned char ch, inbuf[4], outbuf[3];
short i, ixinbuf;
Boolean flignore, flendtext = false;
const unsigned char *tempcstring;
NSMutableData *theData;
if (string == nil) {
return [NSData data];
}
ixtext = 0;
tempcstring = (const unsigned char *)[string UTF8String];
lentext = [string length];
theData = [NSMutableData dataWithCapacity: lentext];
ixinbuf = 0;
while (true) {
if (ixtext >= lentext) {
break;
}
ch = tempcstring [ixtext++];
flignore = false;
if ((ch >= 'A') && (ch <= 'Z')) {
ch = ch - 'A';
}
else if ((ch >= 'a') && (ch <= 'z')) {
ch = ch - 'a' + 26;
}
else if ((ch >= '0') && (ch <= '9')) {
ch = ch - '0' + 52;
}
else if (ch == '+') {
ch = 62;
}
else if (ch == '=') {
flendtext = true;
}
else if (ch == '/') {
ch = 63;
}
else {
flignore = true;
}
if (!flignore) {
short ctcharsinbuf = 3;
Boolean flbreak = false;
if (flendtext) {
if (ixinbuf == 0) {
break;
}
if ((ixinbuf == 1) || (ixinbuf == 2)) {
ctcharsinbuf = 1;
}
else {
ctcharsinbuf = 2;
}
ixinbuf = 3;
flbreak = true;
}
inbuf [ixinbuf++] = ch;
if (ixinbuf == 4) {
ixinbuf = 0;
outbuf[0] = (inbuf[0] << 2) | ((inbuf[1] & 0x30) >> 4);
outbuf[1] = ((inbuf[1] & 0x0F) << 4) | ((inbuf[2] & 0x3C) >> 2);
outbuf[2] = ((inbuf[2] & 0x03) << 6) | (inbuf[3] & 0x3F);
for (i = 0; i < ctcharsinbuf; i++) {
[theData appendBytes: &outbuf[i] length: 1];
}
}
if (flbreak) {
break;
}
}
}
return theData;
}
@end

Просмотреть файл

@ -0,0 +1,112 @@
/*
* NSData+CommonCrypto.h
* AQToolkit
*
* Created by Jim Dovey on 31/8/2008.
*
* Copyright (c) 2008-2009, Jim Dovey
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of this project's author nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#import <Foundation/NSData.h>
#import <Foundation/NSError.h>
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>
extern NSString * const kCommonCryptoErrorDomain;
@interface NSError (CommonCryptoErrorDomain)
+ (NSError *) errorWithCCCryptorStatus: (CCCryptorStatus) status;
@end
@interface NSData (CommonDigest)
- (NSData *) MD2Sum;
- (NSData *) MD4Sum;
- (NSData *) MD5Sum;
- (NSData *) SHA1Hash;
- (NSData *) SHA224Hash;
- (NSData *) SHA256Hash;
- (NSData *) SHA384Hash;
- (NSData *) SHA512Hash;
@end
@interface NSData (CommonCryptor)
- (NSData *) AES256EncryptedDataUsingKey: (id) key error: (NSError **) error;
- (NSData *) decryptedAES256DataUsingKey: (id) key error: (NSError **) error;
- (NSData *) DESEncryptedDataUsingKey: (id) key error: (NSError **) error;
- (NSData *) decryptedDESDataUsingKey: (id) key error: (NSError **) error;
- (NSData *) CASTEncryptedDataUsingKey: (id) key error: (NSError **) error;
- (NSData *) decryptedCASTDataUsingKey: (id) key error: (NSError **) error;
@end
@interface NSData (LowLevelCommonCryptor)
- (NSData *) dataEncryptedUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
error: (CCCryptorStatus *) error;
- (NSData *) dataEncryptedUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
options: (CCOptions) options
error: (CCCryptorStatus *) error;
- (NSData *) dataEncryptedUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
initializationVector: (id) iv // data or string
options: (CCOptions) options
error: (CCCryptorStatus *) error;
- (NSData *) decryptedDataUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
error: (CCCryptorStatus *) error;
- (NSData *) decryptedDataUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
options: (CCOptions) options
error: (CCCryptorStatus *) error;
- (NSData *) decryptedDataUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
initializationVector: (id) iv // data or string
options: (CCOptions) options
error: (CCCryptorStatus *) error;
@end
@interface NSData (CommonHMAC)
- (NSData *) HMACWithAlgorithm: (CCHmacAlgorithm) algorithm;
- (NSData *) HMACWithAlgorithm: (CCHmacAlgorithm) algorithm key: (id) key;
@end

Просмотреть файл

@ -0,0 +1,546 @@
/*
* NSData+CommonCrypto.m
* AQToolkit
*
* Created by Jim Dovey on 31/8/2008.
*
* Copyright (c) 2008-2009, Jim Dovey
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of this project's author nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#import <Foundation/Foundation.h>
#import "NSData+CommonCrypto.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>
NSString * const kCommonCryptoErrorDomain = @"CommonCryptoErrorDomain";
@implementation NSError (CommonCryptoErrorDomain)
+ (NSError *) errorWithCCCryptorStatus: (CCCryptorStatus) status
{
NSString * description = nil, * reason = nil;
switch ( status )
{
case kCCSuccess:
description = NSLocalizedString(@"Success", @"Error description");
break;
case kCCParamError:
description = NSLocalizedString(@"Parameter Error", @"Error description");
reason = NSLocalizedString(@"Illegal parameter supplied to encryption/decryption algorithm", @"Error reason");
break;
case kCCBufferTooSmall:
description = NSLocalizedString(@"Buffer Too Small", @"Error description");
reason = NSLocalizedString(@"Insufficient buffer provided for specified operation", @"Error reason");
break;
case kCCMemoryFailure:
description = NSLocalizedString(@"Memory Failure", @"Error description");
reason = NSLocalizedString(@"Failed to allocate memory", @"Error reason");
break;
case kCCAlignmentError:
description = NSLocalizedString(@"Alignment Error", @"Error description");
reason = NSLocalizedString(@"Input size to encryption algorithm was not aligned correctly", @"Error reason");
break;
case kCCDecodeError:
description = NSLocalizedString(@"Decode Error", @"Error description");
reason = NSLocalizedString(@"Input data did not decode or decrypt correctly", @"Error reason");
break;
case kCCUnimplemented:
description = NSLocalizedString(@"Unimplemented Function", @"Error description");
reason = NSLocalizedString(@"Function not implemented for the current algorithm", @"Error reason");
break;
default:
description = NSLocalizedString(@"Unknown Error", @"Error description");
break;
}
NSMutableDictionary * userInfo = [[NSMutableDictionary alloc] init];
[userInfo setObject: description forKey: NSLocalizedDescriptionKey];
if ( reason != nil )
[userInfo setObject: reason forKey: NSLocalizedFailureReasonErrorKey];
NSError * result = [NSError errorWithDomain: kCommonCryptoErrorDomain code: status userInfo: userInfo];
#if !__has_feature(objc_arc)
[userInfo release];
#endif
return ( result );
}
@end
#pragma mark -
@implementation NSData (CommonDigest)
- (NSData *) MD2Sum
{
unsigned char hash[CC_MD2_DIGEST_LENGTH];
(void) CC_MD2( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_MD2_DIGEST_LENGTH] );
}
- (NSData *) MD4Sum
{
unsigned char hash[CC_MD4_DIGEST_LENGTH];
(void) CC_MD4( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_MD4_DIGEST_LENGTH] );
}
- (NSData *) MD5Sum
{
unsigned char hash[CC_MD5_DIGEST_LENGTH];
(void) CC_MD5( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_MD5_DIGEST_LENGTH] );
}
- (NSData *) SHA1Hash
{
unsigned char hash[CC_SHA1_DIGEST_LENGTH];
(void) CC_SHA1( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_SHA1_DIGEST_LENGTH] );
}
- (NSData *) SHA224Hash
{
unsigned char hash[CC_SHA224_DIGEST_LENGTH];
(void) CC_SHA224( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_SHA224_DIGEST_LENGTH] );
}
- (NSData *) SHA256Hash
{
unsigned char hash[CC_SHA256_DIGEST_LENGTH];
(void) CC_SHA256( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_SHA256_DIGEST_LENGTH] );
}
- (NSData *) SHA384Hash
{
unsigned char hash[CC_SHA384_DIGEST_LENGTH];
(void) CC_SHA384( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_SHA384_DIGEST_LENGTH] );
}
- (NSData *) SHA512Hash
{
unsigned char hash[CC_SHA512_DIGEST_LENGTH];
(void) CC_SHA512( [self bytes], (CC_LONG)[self length], hash );
return ( [NSData dataWithBytes: hash length: CC_SHA512_DIGEST_LENGTH] );
}
@end
@implementation NSData (CommonCryptor)
- (NSData *) AES256EncryptedDataUsingKey: (id) key error: (NSError **) error
{
CCCryptorStatus status = kCCSuccess;
NSData * result = [self dataEncryptedUsingAlgorithm: kCCAlgorithmAES128
key: key
options: kCCOptionPKCS7Padding
error: &status];
if ( result != nil )
return ( result );
if ( error != NULL )
*error = [NSError errorWithCCCryptorStatus: status];
return ( nil );
}
- (NSData *) decryptedAES256DataUsingKey: (id) key error: (NSError **) error
{
CCCryptorStatus status = kCCSuccess;
NSData * result = [self decryptedDataUsingAlgorithm: kCCAlgorithmAES128
key: key
options: kCCOptionPKCS7Padding
error: &status];
if ( result != nil )
return ( result );
if ( error != NULL )
*error = [NSError errorWithCCCryptorStatus: status];
return ( nil );
}
- (NSData *) DESEncryptedDataUsingKey: (id) key error: (NSError **) error
{
CCCryptorStatus status = kCCSuccess;
NSData * result = [self dataEncryptedUsingAlgorithm: kCCAlgorithmDES
key: key
options: kCCOptionPKCS7Padding
error: &status];
if ( result != nil )
return ( result );
if ( error != NULL )
*error = [NSError errorWithCCCryptorStatus: status];
return ( nil );
}
- (NSData *) decryptedDESDataUsingKey: (id) key error: (NSError **) error
{
CCCryptorStatus status = kCCSuccess;
NSData * result = [self decryptedDataUsingAlgorithm: kCCAlgorithmDES
key: key
options: kCCOptionPKCS7Padding
error: &status];
if ( result != nil )
return ( result );
if ( error != NULL )
*error = [NSError errorWithCCCryptorStatus: status];
return ( nil );
}
- (NSData *) CASTEncryptedDataUsingKey: (id) key error: (NSError **) error
{
CCCryptorStatus status = kCCSuccess;
NSData * result = [self dataEncryptedUsingAlgorithm: kCCAlgorithmCAST
key: key
options: kCCOptionPKCS7Padding
error: &status];
if ( result != nil )
return ( result );
if ( error != NULL )
*error = [NSError errorWithCCCryptorStatus: status];
return ( nil );
}
- (NSData *) decryptedCASTDataUsingKey: (id) key error: (NSError **) error
{
CCCryptorStatus status = kCCSuccess;
NSData * result = [self decryptedDataUsingAlgorithm: kCCAlgorithmCAST
key: key
options: kCCOptionPKCS7Padding
error: &status];
if ( result != nil )
return ( result );
if ( error != NULL )
*error = [NSError errorWithCCCryptorStatus: status];
return ( nil );
}
@end
static void FixKeyLengths( CCAlgorithm algorithm, NSMutableData * keyData, NSMutableData * ivData )
{
NSUInteger keyLength = [keyData length];
switch ( algorithm )
{
case kCCAlgorithmAES128:
{
if ( keyLength < 16 )
{
[keyData setLength: 16];
}
else if ( keyLength < 24 )
{
[keyData setLength: 24];
}
else
{
[keyData setLength: 32];
}
break;
}
case kCCAlgorithmDES:
{
[keyData setLength: 8];
break;
}
case kCCAlgorithm3DES:
{
[keyData setLength: 24];
break;
}
case kCCAlgorithmCAST:
{
if ( keyLength < 5 )
{
[keyData setLength: 5];
}
else if ( keyLength > 16 )
{
[keyData setLength: 16];
}
break;
}
case kCCAlgorithmRC4:
{
if ( keyLength > 512 )
[keyData setLength: 512];
break;
}
default:
break;
}
[ivData setLength: [keyData length]];
}
@implementation NSData (LowLevelCommonCryptor)
- (NSData *) _runCryptor: (CCCryptorRef) cryptor result: (CCCryptorStatus *) status
{
size_t bufsize = CCCryptorGetOutputLength( cryptor, (size_t)[self length], true );
void * buf = malloc( bufsize );
size_t bufused = 0;
size_t bytesTotal = 0;
*status = CCCryptorUpdate( cryptor, [self bytes], (size_t)[self length],
buf, bufsize, &bufused );
if ( *status != kCCSuccess )
{
free( buf );
return ( nil );
}
bytesTotal += bufused;
// From Brent Royal-Gordon (Twitter: architechies):
// Need to update buf ptr past used bytes when calling CCCryptorFinal()
*status = CCCryptorFinal( cryptor, buf + bufused, bufsize - bufused, &bufused );
if ( *status != kCCSuccess )
{
free( buf );
return ( nil );
}
bytesTotal += bufused;
return ( [NSData dataWithBytesNoCopy: buf length: bytesTotal] );
}
- (NSData *) dataEncryptedUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key
error: (CCCryptorStatus *) error
{
return ( [self dataEncryptedUsingAlgorithm: algorithm
key: key
initializationVector: nil
options: 0
error: error] );
}
- (NSData *) dataEncryptedUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key
options: (CCOptions) options
error: (CCCryptorStatus *) error
{
return ( [self dataEncryptedUsingAlgorithm: algorithm
key: key
initializationVector: nil
options: options
error: error] );
}
- (NSData *) dataEncryptedUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key
initializationVector: (id) iv
options: (CCOptions) options
error: (CCCryptorStatus *) error
{
CCCryptorRef cryptor = NULL;
CCCryptorStatus status = kCCSuccess;
NSParameterAssert([key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
NSParameterAssert(iv == nil || [iv isKindOfClass: [NSData class]] || [iv isKindOfClass: [NSString class]]);
NSMutableData * keyData, * ivData;
if ( [key isKindOfClass: [NSData class]] )
keyData = (NSMutableData *) [key mutableCopy];
else
keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
if ( [iv isKindOfClass: [NSString class]] )
ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
else
ivData = (NSMutableData *) [iv mutableCopy]; // data or nil
#if !__has_feature(objc_arc)
[keyData autorelease];
[ivData autorelease];
#endif
// ensure correct lengths for key and iv data, based on algorithms
FixKeyLengths( algorithm, keyData, ivData );
status = CCCryptorCreate( kCCEncrypt, algorithm, options,
[keyData bytes], [keyData length], [ivData bytes],
&cryptor );
if ( status != kCCSuccess )
{
if ( error != NULL )
*error = status;
return ( nil );
}
NSData * result = [self _runCryptor: cryptor result: &status];
if ( (result == nil) && (error != NULL) )
*error = status;
CCCryptorRelease( cryptor );
return ( result );
}
- (NSData *) decryptedDataUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
error: (CCCryptorStatus *) error
{
return ( [self decryptedDataUsingAlgorithm: algorithm
key: key
initializationVector: nil
options: 0
error: error] );
}
- (NSData *) decryptedDataUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
options: (CCOptions) options
error: (CCCryptorStatus *) error
{
return ( [self decryptedDataUsingAlgorithm: algorithm
key: key
initializationVector: nil
options: options
error: error] );
}
- (NSData *) decryptedDataUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
initializationVector: (id) iv // data or string
options: (CCOptions) options
error: (CCCryptorStatus *) error
{
CCCryptorRef cryptor = NULL;
CCCryptorStatus status = kCCSuccess;
NSParameterAssert([key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
NSParameterAssert(iv == nil || [iv isKindOfClass: [NSData class]] || [iv isKindOfClass: [NSString class]]);
NSMutableData * keyData, * ivData;
if ( [key isKindOfClass: [NSData class]] )
keyData = (NSMutableData *) [key mutableCopy];
else
keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
if ( [iv isKindOfClass: [NSString class]] )
ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
else
ivData = (NSMutableData *) [iv mutableCopy]; // data or nil
#if !__has_feature(objc_arc)
[keyData autorelease];
[ivData autorelease];
#endif
// ensure correct lengths for key and iv data, based on algorithms
FixKeyLengths( algorithm, keyData, ivData );
status = CCCryptorCreate( kCCDecrypt, algorithm, options,
[keyData bytes], [keyData length], [ivData bytes],
&cryptor );
if ( status != kCCSuccess )
{
if ( error != NULL )
*error = status;
return ( nil );
}
NSData * result = [self _runCryptor: cryptor result: &status];
if ( (result == nil) && (error != NULL) )
*error = status;
CCCryptorRelease( cryptor );
return ( result );
}
@end
@implementation NSData (CommonHMAC)
- (NSData *) HMACWithAlgorithm: (CCHmacAlgorithm) algorithm
{
return ( [self HMACWithAlgorithm: algorithm key: nil] );
}
- (NSData *) HMACWithAlgorithm: (CCHmacAlgorithm) algorithm key: (id) key
{
NSParameterAssert(key == nil || [key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
NSData * keyData = nil;
if ( [key isKindOfClass: [NSString class]] )
keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
else
keyData = (NSData *) key;
// this could be either CC_SHA1_DIGEST_LENGTH or CC_MD5_DIGEST_LENGTH. SHA1 is larger.
unsigned char buf[CC_SHA1_DIGEST_LENGTH];
CCHmac( algorithm, [keyData bytes], [keyData length], [self bytes], [self length], buf );
return ( [NSData dataWithBytes: buf length: (algorithm == kCCHmacAlgMD5 ? CC_MD5_DIGEST_LENGTH : CC_SHA1_DIGEST_LENGTH)] );
}
@end

Просмотреть файл

@ -0,0 +1,15 @@
//
// NSString+Base64.h
// Gurpartap Singh
//
// Created by Gurpartap Singh on 06/05/12.
// Copyright (c) 2012 Gurpartap Singh. All rights reserved.
//
#import <Foundation/NSString.h>
@interface NSString (Base64Additions)
+ (NSString *)base64StringFromData:(NSData *)data length:(NSUInteger)length;
@end

Просмотреть файл

@ -0,0 +1,83 @@
//
// NSStringAdditions.m
// Gurpartap Singh
//
// Created by Gurpartap Singh on 06/05/12.
// Copyright (c) 2012 Gurpartap Singh. All rights reserved.
//
#import "NSString+Base64.h"
#import <Foundation/Foundation.h>
static char base64EncodingTable[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
@implementation NSString (Base64Additions)
+ (NSString *)base64StringFromData: (NSData *)data length: (NSUInteger)length {
unsigned long ixtext, lentext;
long ctremaining;
unsigned char input[3], output[4];
short i, charsonline = 0, ctcopy;
const unsigned char *raw;
NSMutableString *result;
lentext = [data length];
if (lentext < 1) {
return @"";
}
result = [NSMutableString stringWithCapacity: lentext];
raw = [data bytes];
ixtext = 0;
while (true) {
ctremaining = lentext - ixtext;
if (ctremaining <= 0) {
break;
}
for (i = 0; i < 3; i++) {
unsigned long ix = ixtext + i;
if (ix < lentext) {
input[i] = raw[ix];
}
else {
input[i] = 0;
}
}
output[0] = (input[0] & 0xFC) >> 2;
output[1] = ((input[0] & 0x03) << 4) | ((input[1] & 0xF0) >> 4);
output[2] = ((input[1] & 0x0F) << 2) | ((input[2] & 0xC0) >> 6);
output[3] = input[2] & 0x3F;
ctcopy = 4;
switch (ctremaining) {
case 1:
ctcopy = 2;
break;
case 2:
ctcopy = 3;
break;
}
for (i = 0; i < ctcopy; i++) {
[result appendString: [NSString stringWithFormat: @"%c", base64EncodingTable[output[i]]]];
}
for (i = ctcopy; i < 4; i++) {
[result appendString: @"="];
}
ixtext += 3;
charsonline += 4;
if ((length > 0) && (charsonline >= length)) {
charsonline = 0;
}
}
return result;
}
@end

Просмотреть файл

@ -0,0 +1,19 @@
//
// AFViewShaker
// AFViewShaker
//
// Created by Philip Vasilchenko on 03.12.13.
// Copyright (c) 2014 okolodev. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AFViewShaker : NSObject <CAAnimationDelegate>
- (instancetype)initWithView:(UIView *)view;
- (instancetype)initWithViewsArray:(NSArray *)viewsArray;
- (void)shake;
- (void)shakeWithDuration:(NSTimeInterval)duration completion:(void (^)())completion;
@end

Просмотреть файл

@ -0,0 +1,81 @@
//
// AFViewShaker
// AFViewShaker
//
// Created by Philip Vasilchenko on 03.12.13.
// Copyright (c) 2014 okolodev. All rights reserved.
//
#import "AFViewShaker.h"
static NSTimeInterval const kAFViewShakerDefaultDuration = 0.5;
static NSString * const kAFViewShakerAnimationKey = @"kAFViewShakerAnimationKey";
@interface AFViewShaker ()
@property (nonatomic, strong) NSArray * views;
@property (nonatomic, assign) NSUInteger completedAnimations;
@property (nonatomic, copy) void (^completionBlock)();
@end
@implementation AFViewShaker
- (instancetype)initWithView:(UIView *)view {
return [self initWithViewsArray:@[ view ]];
}
- (instancetype)initWithViewsArray:(NSArray *)viewsArray {
self = [super init];
if ( self ) {
self.views = viewsArray;
}
return self;
}
#pragma mark - Public methods
- (void)shake {
[self shakeWithDuration:kAFViewShakerDefaultDuration completion:nil];
}
- (void)shakeWithDuration:(NSTimeInterval)duration completion:(void (^)())completion {
self.completionBlock = completion;
for (UIView * view in self.views) {
[self addShakeAnimationForView:view withDuration:duration];
}
}
#pragma mark - Shake Animation
- (void)addShakeAnimationForView:(UIView *)view withDuration:(NSTimeInterval)duration {
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.x"];
CGFloat currentTx = view.transform.tx;
animation.delegate = self;
animation.duration = duration;
animation.values = @[ @(currentTx), @(currentTx + 10), @(currentTx-8), @(currentTx + 8), @(currentTx -5), @(currentTx + 5), @(currentTx) ];
animation.keyTimes = @[ @(0), @(0.225), @(0.425), @(0.6), @(0.75), @(0.875), @(1) ];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[view.layer addAnimation:animation forKey:kAFViewShakerAnimationKey];
}
#pragma mark - CAAnimation Delegate
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
self.completedAnimations += 1;
if ( self.completedAnimations >= self.views.count ) {
self.completedAnimations = 0;
if ( self.completionBlock ) {
self.completionBlock();
}
}
}
@end

Просмотреть файл

@ -0,0 +1,116 @@
//
// AHKActionSheet.h
// AHKActionSheetExample
//
// Created by Arkadiusz on 08-04-14.
// Copyright (c) 2014 Arkadiusz Holko. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, AHKActionSheetButtonType) {
AHKActionSheetButtonTypeDefault = 0,
AHKActionSheetButtonTypeDisabled,
AHKActionSheetButtonTypeDestructive,
AHKActionSheetButtonTypeCrypto
};
@class AHKActionSheet;
typedef void(^AHKActionSheetHandler)(AHKActionSheet *actionSheet);
/// A block-based alternative to the `UIAlertView`.
@interface AHKActionSheet : UIView <UIAppearanceContainer>
// Appearance - all of the following properties should be set before showing the action sheet. See `+initialize` to learn the default values of all properties.
/**
* See UIImage+AHKAdditions.h/.m to learn how these three properties are used.
*/
@property (nonatomic) CGFloat blurRadius UI_APPEARANCE_SELECTOR;
@property (strong, nonatomic) UIColor *blurTintColor UI_APPEARANCE_SELECTOR;
@property (nonatomic) CGFloat blurSaturationDeltaFactor UI_APPEARANCE_SELECTOR;
/// Height of the button (internally it's a `UITableViewCell`).
@property (nonatomic) CGFloat buttonHeight UI_APPEARANCE_SELECTOR;
/// Height of the cancel button.
@property (nonatomic) CGFloat cancelButtonHeight UI_APPEARANCE_SELECTOR;
/**
* If set, a small shadow (a gradient layer) will be drawn above the cancel button to separate it visually from the other buttons.
* It's best to use a color similar (but maybe with a lower alpha value) to blurTintColor.
* See "Advanced" example in the example project to see it used.
*/
@property (strong, nonatomic) UIColor *cancelButtonShadowColor UI_APPEARANCE_SELECTOR;
/// Boxed (@YES, @NO) boolean value (enabled by default). Isn't supported on iOS 6.
@property (strong, nonatomic) NSNumber *automaticallyTintButtonImages UI_APPEARANCE_SELECTOR;
/// Boxed boolean value. Useful when adding buttons without images (in that case text looks better centered). Disabled by default.
@property (strong, nonatomic) NSNumber *buttonTextCenteringEnabled UI_APPEARANCE_SELECTOR;
/// Color of the separator between buttons.
@property (strong, nonatomic) UIColor *separatorColor UI_APPEARANCE_SELECTOR;
/// Background color of the button when it's tapped (internally it's a UITableViewCell)
@property (strong, nonatomic) UIColor *selectedBackgroundColor UI_APPEARANCE_SELECTOR;
/// Text attributes of the title (passed in initWithTitle: or set via `title` property)
@property (copy, nonatomic) NSDictionary *titleTextAttributes UI_APPEARANCE_SELECTOR;
@property (copy, nonatomic) NSDictionary *buttonTextAttributes UI_APPEARANCE_SELECTOR;
@property (copy, nonatomic) NSDictionary *disabledButtonTextAttributes UI_APPEARANCE_SELECTOR;
@property (copy, nonatomic) NSDictionary *destructiveButtonTextAttributes UI_APPEARANCE_SELECTOR;
@property (copy, nonatomic) NSDictionary *cryptoButtonTextAttributes UI_APPEARANCE_SELECTOR;
@property (copy, nonatomic) NSDictionary *cancelButtonTextAttributes UI_APPEARANCE_SELECTOR;
/// Duration of the show/dismiss animations. Defaults to 0.5.
@property (nonatomic) NSTimeInterval animationDuration UI_APPEARANCE_SELECTOR;
/// Boxed boolean value. Enables/disables control hiding with pan gesture. Enabled by default.
@property (strong, nonatomic) NSNumber *cancelOnPanGestureEnabled UI_APPEARANCE_SELECTOR;
/// Boxed boolean value. Enables/disables control hiding when tapped on empty area. Disabled by default.
@property (strong, nonatomic) NSNumber *cancelOnTapEmptyAreaEnabled UI_APPEARANCE_SELECTOR;
/// A handler called on every type of dismissal (tapping on "Cancel" or swipe down or flick down).
@property (strong, nonatomic) AHKActionSheetHandler cancelHandler;
@property (copy, nonatomic) NSString *cancelButtonTitle;
/// String to display above the buttons.
@property (copy, nonatomic) NSString *title;
/// View to display above the buttons (only if the title isn't set).
@property (strong, nonatomic) UIView *headerView;
/// Window visible before the actionSheet was presented.
@property (weak, nonatomic, readonly) UIWindow *previousKeyWindow;
///TWS
@property (nonatomic, weak) UIView *view;
/**
* Initializes the action sheet with a specified title. `headerView` can be used if a string is insufficient for the title; set `title` as `nil` in this case.
*
* It's the designated initializer.
*
* @param title A string to display in the title area, above the buttons.
*
* @return A newly initialized action sheet.
*/
- (instancetype)initWithTitle:(NSString *)title;
- (instancetype)initWithView:(UIView *)view title:(NSString *)title;
/**
* Adds a button without an image. Has to be called before showing the action sheet.
*
* @param handler A completion handler block to execute when a dismissal animation (after the user tapped on the button) has finished.
*/
- (void)addButtonWithTitle:(NSString *)title type:(AHKActionSheetButtonType)type handler:(AHKActionSheetHandler)handler;
/**
* Adds a button with an image. Has to be called before showing the action sheet.
*
* @param image The image to display on the left of the title.
* @param handler A completion handler block to execute when a dismissal animation (after the user tapped on the button) has finished.
*/
- (void)addButtonWithTitle:(NSString *)title image:(UIImage *)image type:(AHKActionSheetButtonType)type handler:(AHKActionSheetHandler)handler;
/// Displays the action sheet.
- (void)show;
/// Dismisses the action sheet with an optional animation.
- (void)dismissAnimated:(BOOL)animated;
@end

Просмотреть файл

@ -0,0 +1,591 @@
//
// AHKActionSheet.m
// AHKActionSheetExample
//
// Created by Arkadiusz on 08-04-14.
// Copyright (c) 2014 Arkadiusz Holko. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "AHKActionSheet.h"
#import "AHKActionSheetViewController.h"
#import "UIImage+AHKAdditions.h"
#import "UIWindow+AHKAdditions.h"
static const NSTimeInterval kDefaultAnimationDuration = 0.5f;
// Length of the range at which the blurred background is being hidden when the user scrolls the tableView to the top.
static const CGFloat kBlurFadeRangeSize = 200.0f;
static NSString * const kCellIdentifier = @"Cell";
// How much user has to scroll beyond the top of the tableView for the view to dismiss automatically.
static const CGFloat kAutoDismissOffset = 80.0f;
// Offset at which there's a check if the user is flicking the tableView down.
static const CGFloat kFlickDownHandlingOffset = 20.0f;
static const CGFloat kFlickDownMinVelocity = 2000.0f;
// How much free space to leave at the top (above the tableView's contents) when there's a lot of elements. It makes this control look similar to the UIActionSheet.
static const CGFloat kTopSpaceMarginFraction = 0.0; //TWS 0.333f;
// cancelButton's shadow height as the ratio to the cancelButton's height
static const CGFloat kCancelButtonShadowHeightRatio = 0.333f;
/// Used for storing button configuration.
@interface AHKActionSheetItem : NSObject
@property (copy, nonatomic) NSString *title;
@property (strong, nonatomic) UIImage *image;
@property (nonatomic) AHKActionSheetButtonType type;
@property (strong, nonatomic) AHKActionSheetHandler handler;
@end
@implementation AHKActionSheetItem
@end
@interface AHKActionSheet() <UITableViewDataSource, UITableViewDelegate, UIGestureRecognizerDelegate>
@property (strong, nonatomic) NSMutableArray *items;
@property (weak, nonatomic, readwrite) UIWindow *previousKeyWindow;
@property (strong, nonatomic) UIWindow *window;
@property (weak, nonatomic) UIImageView *blurredBackgroundView;
@property (weak, nonatomic) UITableView *tableView;
@property (weak, nonatomic) UIButton *cancelButton;
@property (weak, nonatomic) UIView *cancelButtonShadowView;
@end
@implementation AHKActionSheet
#pragma mark - Init
+ (void)initialize
{
if (self != [AHKActionSheet class]) {
return;
}
AHKActionSheet *appearance = [self appearance];
[appearance setBlurRadius:16.0f];
[appearance setBlurTintColor:[UIColor colorWithWhite:1.0f alpha:0.5f]];
[appearance setBlurSaturationDeltaFactor:1.8f];
[appearance setButtonHeight:60.0f];
[appearance setCancelButtonHeight:44.0f];
[appearance setAutomaticallyTintButtonImages:@YES];
[appearance setSelectedBackgroundColor:[UIColor colorWithWhite:0.1f alpha:0.2f]];
[appearance setCancelButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:17.0f],
NSForegroundColorAttributeName : [UIColor darkGrayColor] }];
[appearance setButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:17.0f]}];
[appearance setDisabledButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:14.0f],
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.6f alpha:1.0] }];
[appearance setDestructiveButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:17.0f],
NSForegroundColorAttributeName : [UIColor redColor] }];
[appearance setTitleTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:14.0f],
NSForegroundColorAttributeName : [UIColor grayColor] }];
[appearance setCancelOnPanGestureEnabled:@(YES)];
[appearance setCancelOnTapEmptyAreaEnabled:@(NO)];
[appearance setAnimationDuration:kDefaultAnimationDuration];
}
- (instancetype)initWithTitle:(NSString *)title
{
self = [super init];
if (self) {
_title = [title copy];
_cancelButtonTitle = @"Cancel";
}
return self;
}
- (instancetype)initWithView:(UIView *)view title:(NSString *)title
{
self = [super init];
if (self) {
_title = [title copy];
_cancelButtonTitle = @"Cancel";
_view = view;
}
return self;
}
- (instancetype)init
{
return [self initWithTitle:nil];
}
- (void)dealloc
{
self.tableView.dataSource = nil;
self.tableView.delegate = nil;
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return (NSInteger)[self.items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];
//TWS
cell.separatorInset = UIEdgeInsetsMake(0.f, 55.f, 0.f, 0.f);
AHKActionSheetItem *item = self.items[(NSUInteger)indexPath.row];
NSDictionary *attributes = nil;
switch (item.type)
{
case AHKActionSheetButtonTypeDefault:
attributes = self.buttonTextAttributes;
break;
case AHKActionSheetButtonTypeDisabled:
attributes = self.disabledButtonTextAttributes;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
break;
case AHKActionSheetButtonTypeDestructive:
attributes = self.destructiveButtonTextAttributes;
break;
case AHKActionSheetButtonTypeCrypto:
attributes = self.cryptoButtonTextAttributes;
break;
}
NSAttributedString *attrTitle = [[NSAttributedString alloc] initWithString:item.title attributes:attributes];
cell.textLabel.attributedText = attrTitle;
cell.textLabel.textAlignment = [self.buttonTextCenteringEnabled boolValue] ? NSTextAlignmentCenter : NSTextAlignmentLeft;
// Use image with template mode with color the same as the text (when enabled).
BOOL useTemplateMode = [UIImage instancesRespondToSelector:@selector(imageWithRenderingMode:)] && [self.automaticallyTintButtonImages boolValue];
cell.imageView.image = useTemplateMode ? [item.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] : item.image;
if ([UIImageView instancesRespondToSelector:@selector(tintColor)]){
cell.imageView.tintColor = attributes[NSForegroundColorAttributeName] ? attributes[NSForegroundColorAttributeName] : [UIColor blackColor];
}
//TWS
cell.backgroundColor = [UIColor whiteColor];
if (self.selectedBackgroundColor && ![cell.selectedBackgroundView.backgroundColor isEqual:self.selectedBackgroundColor]) {
cell.selectedBackgroundView = [[UIView alloc] init];
cell.selectedBackgroundView.backgroundColor = self.selectedBackgroundColor;
}
return cell;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
AHKActionSheetItem *item = self.items[(NSUInteger)indexPath.row];
if (item.type != AHKActionSheetButtonTypeDisabled) {
[self dismissAnimated:YES duration:self.animationDuration completion:item.handler];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return self.buttonHeight;
}
//TWS
/*
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// Remove separator inset as described here: http://stackoverflow.com/a/25877725/783960
if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
// Prevent the cell from inheriting the Table View's margin settings
if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
// Explictly set your cell's layout margins
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
*/
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (![self.cancelOnPanGestureEnabled boolValue]) {
return;
}
[self fadeBlursOnScrollToTop];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (![self.cancelOnPanGestureEnabled boolValue]) {
return;
}
CGPoint scrollVelocity = [scrollView.panGestureRecognizer velocityInView:self];
BOOL viewWasFlickedDown = scrollVelocity.y > kFlickDownMinVelocity && scrollView.contentOffset.y < -self.tableView.contentInset.top - kFlickDownHandlingOffset;
BOOL shouldSlideDown = scrollView.contentOffset.y < -self.tableView.contentInset.top - kAutoDismissOffset;
if (viewWasFlickedDown) {
// use a shorter duration for a flick down animation
static const NSTimeInterval duration = 0.2f;
[self dismissAnimated:YES duration:duration completion:self.cancelHandler];
} else if (shouldSlideDown) {
[self dismissAnimated:YES duration:self.animationDuration completion:self.cancelHandler];
}
}
#pragma mark - Properties
- (NSMutableArray *)items
{
if (!_items) {
_items = [NSMutableArray array];
}
return _items;
}
#pragma mark - Actions
- (void)cancelButtonTapped:(id)sender
{
[self dismissAnimated:YES duration:self.animationDuration completion:self.cancelHandler];
}
#pragma mark - Public
- (void)addButtonWithTitle:(NSString *)title type:(AHKActionSheetButtonType)type handler:(AHKActionSheetHandler)handler
{
[self addButtonWithTitle:title image:nil type:type handler:handler];
}
- (void)addButtonWithTitle:(NSString *)title image:(UIImage *)image type:(AHKActionSheetButtonType)type handler:(AHKActionSheetHandler)handler
{
AHKActionSheetItem *item = [[AHKActionSheetItem alloc] init];
item.title = title;
item.image = image;
item.type = type;
item.handler = handler;
[self.items addObject:item];
}
- (void)show
{
NSAssert([self.items count] > 0, @"Please add some buttons before calling -show.");
if ([self isVisible]) {
return;
}
self.previousKeyWindow = [UIApplication sharedApplication].keyWindow;
UIImage *previousKeyWindowSnapshot = [self.previousKeyWindow ahk_snapshot];
[self setUpNewWindow];
[self setUpBlurredBackgroundWithSnapshot:previousKeyWindowSnapshot];
[self setUpCancelButton];
[self setUpTableView];
if (self.cancelOnPanGestureEnabled.boolValue) {
[self setUpCancelTapGestureForView:self.tableView];
}
CGFloat slideDownMinOffset = (CGFloat)fmin(CGRectGetHeight(self.frame) + self.tableView.contentOffset.y, CGRectGetHeight(self.frame));
self.tableView.transform = CGAffineTransformMakeTranslation(0, slideDownMinOffset);
void(^immediateAnimations)(void) = ^(void) {
self.blurredBackgroundView.alpha = 1.0f;
};
void(^delayedAnimations)(void) = ^(void) {
self.cancelButton.frame = CGRectMake(0,
CGRectGetMaxY(self.bounds) - self.cancelButtonHeight,
CGRectGetWidth(_view.bounds),
self.cancelButtonHeight);
//TWS
self.cancelButton.backgroundColor = [UIColor whiteColor];
self.tableView.transform = CGAffineTransformMakeTranslation(0, 0);
// manual calculation of table's contentSize.height
CGFloat tableContentHeight = [self.items count] * self.buttonHeight + CGRectGetHeight(self.tableView.tableHeaderView.frame);
CGFloat topInset;
BOOL buttonsFitInWithoutScrolling = tableContentHeight < CGRectGetHeight(self.tableView.frame) * (1.0 - kTopSpaceMarginFraction);
if (buttonsFitInWithoutScrolling) {
// show all buttons if there isn't many
topInset = CGRectGetHeight(self.tableView.frame) - tableContentHeight;
} else {
// leave an empty space on the top to make the control look similar to UIActionSheet
topInset = (CGFloat)round(CGRectGetHeight(self.tableView.frame) * kTopSpaceMarginFraction);
}
self.tableView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0);
self.tableView.bounces = [self.cancelOnPanGestureEnabled boolValue] || !buttonsFitInWithoutScrolling;
};
if ([UIView respondsToSelector:@selector(animateKeyframesWithDuration:delay:options:animations:completion:)]){
// Animate sliding in tableView and cancel button with keyframe animation for a nicer effect.
[UIView animateKeyframesWithDuration:self.animationDuration delay:0 options:0 animations:^{
immediateAnimations();
[UIView addKeyframeWithRelativeStartTime:0.3f relativeDuration:0.7f animations:^{
delayedAnimations();
}];
} completion:nil];
} else {
[UIView animateWithDuration:self.animationDuration animations:^{
immediateAnimations();
delayedAnimations();
}];
}
}
- (void)dismissAnimated:(BOOL)animated
{
[self dismissAnimated:animated duration:self.animationDuration completion:self.cancelHandler];
}
#pragma mark - Private
- (BOOL)isVisible
{
// action sheet is visible iff it's associated with a window
return !!self.window;
}
- (void)dismissAnimated:(BOOL)animated duration:(NSTimeInterval)duration completion:(AHKActionSheetHandler)completionHandler
{
if (![self isVisible]) {
return;
}
// delegate isn't needed anymore because tableView will be hidden (and we don't want delegate methods to be called now)
self.tableView.delegate = nil;
self.tableView.userInteractionEnabled = NO;
// keep the table from scrolling back up
self.tableView.contentInset = UIEdgeInsetsMake(-self.tableView.contentOffset.y, 0, 0, 0);
void(^tearDownView)(void) = ^(void) {
// remove the views because it's easiest to just recreate them if the action sheet is shown again
for (UIView *view in @[self.tableView, self.cancelButton, self.blurredBackgroundView, self.window]) {
[view removeFromSuperview];
}
self.window = nil;
[self.previousKeyWindow makeKeyAndVisible];
if (completionHandler) {
completionHandler(self);
}
};
if (animated) {
// animate sliding down tableView and cancelButton.
[UIView animateWithDuration:duration animations:^{
self.blurredBackgroundView.alpha = 0.0f;
self.cancelButton.transform = CGAffineTransformTranslate(self.cancelButton.transform, 0, self.cancelButtonHeight);
self.cancelButtonShadowView.alpha = 0.0f;
// Shortest shift of position sufficient to hide all tableView contents below the bottom margin.
// contentInset isn't used here (unlike in -show) because it caused weird problems with animations in some cases.
CGFloat slideDownMinOffset = (CGFloat)fmin(CGRectGetHeight(self.frame) + self.tableView.contentOffset.y, CGRectGetHeight(self.frame));
self.tableView.transform = CGAffineTransformMakeTranslation(0, slideDownMinOffset);
} completion:^(BOOL finished) {
tearDownView();
}];
} else {
tearDownView();
}
}
- (void)setUpNewWindow
{
AHKActionSheetViewController *actionSheetVC = [[AHKActionSheetViewController alloc] initWithNibName:nil bundle:nil];
actionSheetVC.actionSheet = self;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.window.opaque = NO;
self.window.rootViewController = actionSheetVC;
[self.window makeKeyAndVisible];
}
- (void)setUpBlurredBackgroundWithSnapshot:(UIImage *)previousKeyWindowSnapshot
{
UIImage *blurredViewSnapshot = [previousKeyWindowSnapshot
ahk_applyBlurWithRadius:self.blurRadius
tintColor:self.blurTintColor
saturationDeltaFactor:self.blurSaturationDeltaFactor
maskImage:nil];
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:blurredViewSnapshot];
backgroundView.frame = self.bounds;
backgroundView.alpha = 0.0f;
[self addSubview:backgroundView];
self.blurredBackgroundView = backgroundView;
}
- (void)setUpCancelTapGestureForView:(UIView*)view {
UITapGestureRecognizer *cancelTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cancelButtonTapped:)];
cancelTap.delegate = self;
[view addGestureRecognizer:cancelTap];
}
- (void)setUpCancelButton
{
UIButton *cancelButton;
// It's hard to check if UIButtonTypeSystem enumeration exists, so we're checking existence of another method that was introduced in iOS 7.
if ([UIView instancesRespondToSelector:@selector(tintAdjustmentMode)]) {
cancelButton= [UIButton buttonWithType:UIButtonTypeSystem];
} else {
cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
}
NSAttributedString *attrTitle = [[NSAttributedString alloc] initWithString:self.cancelButtonTitle
attributes:self.cancelButtonTextAttributes];
[cancelButton setAttributedTitle:attrTitle forState:UIControlStateNormal];
[cancelButton addTarget:self action:@selector(cancelButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
cancelButton.frame = CGRectMake(0,
CGRectGetMaxY(self.bounds) - self.cancelButtonHeight,
CGRectGetWidth(self.bounds),
self.cancelButtonHeight);
// move the button below the screen (ready to be animated -show)
cancelButton.transform = CGAffineTransformMakeTranslation(0, self.cancelButtonHeight);
cancelButton.clipsToBounds = YES;
[self addSubview:cancelButton];
self.cancelButton = cancelButton;
// add a small shadow/glow above the button
if (self.cancelButtonShadowColor) {
self.cancelButton.clipsToBounds = NO;
CGFloat gradientHeight = (CGFloat)round(self.cancelButtonHeight * kCancelButtonShadowHeightRatio);
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, -gradientHeight, CGRectGetWidth(self.bounds), gradientHeight)];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = @[ (id)[UIColor colorWithWhite:0.0 alpha:0.0].CGColor, (id)[self.blurTintColor colorWithAlphaComponent:0.1f].CGColor ];
[view.layer insertSublayer:gradient atIndex:0];
[self.cancelButton addSubview:view];
self.cancelButtonShadowView = view;
}
}
- (void)setUpTableView
{
CGRect statusBarViewRect = [self convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil];
CGFloat statusBarHeight = CGRectGetHeight(statusBarViewRect);
CGRect frame = CGRectMake(0,
statusBarHeight,
CGRectGetWidth(_view.bounds),
CGRectGetHeight(self.bounds) - statusBarHeight - self.cancelButtonHeight);
UITableView *tableView = [[UITableView alloc] initWithFrame:frame];
tableView.backgroundColor = [UIColor clearColor];
tableView.showsVerticalScrollIndicator = NO;
if (self.separatorColor) {
tableView.separatorColor = self.separatorColor;
}
tableView.delegate = self;
tableView.dataSource = self;
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier];
[self insertSubview:tableView aboveSubview:self.blurredBackgroundView];
// move the content below the screen, ready to be animated in -show
tableView.contentInset = UIEdgeInsetsMake(CGRectGetHeight(self.bounds), 0, 0, 0);
// removes separators below the footer (between empty cells)
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
self.tableView = tableView;
[self setUpTableViewHeader];
}
- (void)setUpTableViewHeader
{
if (self.title) {
// paddings similar to those in the UITableViewCell
static const CGFloat leftRightPadding = 15.0f;
static const CGFloat topBottomPadding = 8.0f;
CGFloat labelWidth = CGRectGetWidth(self.bounds) - 2*leftRightPadding;
NSAttributedString *attrText = [[NSAttributedString alloc] initWithString:self.title attributes:self.titleTextAttributes];
// create a label and calculate its size
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0;
[label setAttributedText:attrText];
CGSize labelSize = [label sizeThatFits:CGSizeMake(labelWidth, MAXFLOAT)];
label.frame = CGRectMake(leftRightPadding, topBottomPadding, labelWidth, labelSize.height);
label.backgroundColor = [UIColor clearColor];
// create and add a header consisting of the label
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), labelSize.height + 2*topBottomPadding)];
[headerView addSubview:label];
self.tableView.tableHeaderView = headerView;
} else if (self.headerView) {
self.tableView.tableHeaderView = self.headerView;
}
// add a separator between the tableHeaderView and a first row (technically at the bottom of the tableHeaderView)
if (self.tableView.tableHeaderView && self.tableView.separatorStyle != UITableViewCellSeparatorStyleNone) {
CGFloat separatorHeight = 1.0f / [UIScreen mainScreen].scale;
CGRect separatorFrame = CGRectMake(0,
CGRectGetHeight(self.tableView.tableHeaderView.frame) - separatorHeight,
CGRectGetWidth(self.tableView.tableHeaderView.frame),
separatorHeight);
UIView *separator = [[UIView alloc] initWithFrame:separatorFrame];
separator.backgroundColor = self.tableView.separatorColor;
//TWS
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.tableView.tableHeaderView.frame), 1 / UIScreen.mainScreen.scale)];
line.backgroundColor = self.tableView.separatorColor;
[self.tableView.tableHeaderView addSubview:separator];
[self.tableView.tableHeaderView addSubview:line];
}
}
- (void)fadeBlursOnScrollToTop
{
if (self.tableView.isDragging || self.tableView.isDecelerating) {
CGFloat alphaWithoutBounds = 1.0f - ( -(self.tableView.contentInset.top + self.tableView.contentOffset.y) / kBlurFadeRangeSize);
// limit alpha to the interval [0, 1]
CGFloat alpha = (CGFloat)fmax(fmin(alphaWithoutBounds, 1.0f), 0.0f);
self.blurredBackgroundView.alpha = alpha;
self.cancelButtonShadowView.alpha = alpha;
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// If the view that is touched is not the view associated with this view's table view, but
// is one of the sub-views, we should not recognize the touch.
// Original source: http://stackoverflow.com/questions/10755566/how-to-know-uitableview-is-pressed-when-empty
if (touch.view != self.tableView && [touch.view isDescendantOfView:self.tableView]) {
return NO;
}
return YES;
}
@end

Просмотреть файл

@ -0,0 +1,15 @@
//
// AHKActionSheetViewController.h
// AHKActionSheetExample
//
// Created by Arkadiusz on 09-04-14.
// Copyright (c) 2014 Arkadiusz Holko. All rights reserved.
//
#import <UIKit/UIKit.h>
@class AHKActionSheet;
@interface AHKActionSheetViewController : UIViewController
@property (strong, nonatomic) AHKActionSheet *actionSheet;
@end

Просмотреть файл

@ -0,0 +1,77 @@
//
// AHKActionSheetViewController.m
// AHKActionSheetExample
//
// Created by Arkadiusz on 09-04-14.
// Copyright (c) 2014 Arkadiusz Holko. All rights reserved.
//
#import "AHKActionSheetViewController.h"
#import "AHKActionSheet.h"
#import "UIWindow+AHKAdditions.h"
@interface AHKActionSheetViewController ()
@property (nonatomic) BOOL viewAlreadyAppear;
@end
@implementation AHKActionSheetViewController
#pragma mark - UIViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:self.actionSheet];
self.actionSheet.frame = self.view.bounds;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.viewAlreadyAppear = YES;
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.actionSheet.frame = self.view.bounds;
}
- (BOOL)shouldAutorotate
{
// doesn't allow autorotation after the view did appear (rotation messes up a blurred background)
return !self.viewAlreadyAppear;
}
//TWS
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 90000
- (NSUInteger)supportedInterfaceOrientations
#else
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
#endif
{
return UIInterfaceOrientationMaskAll;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
UIWindow *window = self.actionSheet.previousKeyWindow;
if (!window) {
window = [[UIApplication sharedApplication].windows firstObject];
}
return [[window ahk_viewControllerForStatusBarStyle] preferredStatusBarStyle];
}
- (BOOL)prefersStatusBarHidden
{
UIWindow *window = self.actionSheet.previousKeyWindow;
if (!window) {
window = [[UIApplication sharedApplication].windows firstObject];
}
return [[window ahk_viewControllerForStatusBarHidden] prefersStatusBarHidden];
}
@end

Просмотреть файл

@ -0,0 +1,107 @@
/*
File: UIImage+ImageEffects.h
Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code youll want to look out to find out how to use vImage to efficiently calculate a blur.
Version: 1.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2013 Apple Inc. All Rights Reserved.
Copyright © 2013 Apple Inc. All rights reserved.
WWDC 2013 License
NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013
Session. Please refer to the applicable WWDC 2013 Session for further
information.
IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and
your use, installation, modification or redistribution of this Apple
software constitutes acceptance of these terms. If you do not agree with
these terms, please do not use, install, modify or redistribute this
Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple
Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES
NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EA1002
5/3/2013
*/
@import UIKit;
@interface UIImage (AHKAdditions)
- (UIImage *)ahk_applyLightEffect;
- (UIImage *)ahk_applyExtraLightEffect;
- (UIImage *)ahk_applyDarkEffect;
- (UIImage *)ahk_applyTintEffectWithColor:(UIColor *)tintColor;
- (UIImage *)ahk_applyBlurWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage;
@end

Просмотреть файл

@ -0,0 +1,278 @@
/*
File: UIImage+ImageEffects.m
Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code youll want to look out to find out how to use vImage to efficiently calculate a blur.
Version: 1.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2013 Apple Inc. All Rights Reserved.
Copyright © 2013 Apple Inc. All rights reserved.
WWDC 2013 License
NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013
Session. Please refer to the applicable WWDC 2013 Session for further
information.
IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and
your use, installation, modification or redistribution of this Apple
software constitutes acceptance of these terms. If you do not agree with
these terms, please do not use, install, modify or redistribute this
Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple
Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES
NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EA1002
5/3/2013
*/
#import "UIImage+AHKAdditions.h"
@import Accelerate;
#import <float.h>
@implementation UIImage (AHKAdditions)
- (UIImage *)ahk_applyLightEffect
{
UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3f];
return [self ahk_applyBlurWithRadius:30 tintColor:tintColor saturationDeltaFactor:1.8f maskImage:nil];
}
- (UIImage *)ahk_applyExtraLightEffect
{
UIColor *tintColor = [UIColor colorWithWhite:0.97f alpha:0.82f];
return [self ahk_applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8f maskImage:nil];
}
- (UIImage *)ahk_applyDarkEffect
{
UIColor *tintColor = [UIColor colorWithWhite:0.11f alpha:0.73f];
return [self ahk_applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8f maskImage:nil];
}
- (UIImage *)ahk_applyTintEffectWithColor:(UIColor *)tintColor
{
const CGFloat EffectColorAlpha = 0.6f;
UIColor *effectColor = tintColor;
int componentCount = (int)CGColorGetNumberOfComponents(tintColor.CGColor);
if (componentCount == 2) {
CGFloat b;
if ([tintColor getWhite:&b alpha:NULL]) {
effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
}
}
else {
CGFloat r, g, b;
if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
}
}
return [self ahk_applyBlurWithRadius:10 tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil];
}
- (UIImage *)ahk_applyBlurWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage
{
// Check pre-conditions.
if (self.size.width < 1 || self.size.height < 1) {
NSLog (@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
return nil;
}
if (!self.CGImage) {
NSLog (@"*** error: image must be backed by a CGImage: %@", self);
return nil;
}
if (maskImage && !maskImage.CGImage) {
NSLog (@"*** error: maskImage must be backed by a CGImage: %@", maskImage);
return nil;
}
CGRect imageRect = { CGPointZero, self.size };
UIImage *effectImage = self;
BOOL hasBlur = blurRadius > __FLT_EPSILON__;
BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
if (hasBlur || hasSaturationChange) {
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef effectInContext = UIGraphicsGetCurrentContext();
CGContextScaleCTM(effectInContext, 1.0, -1.0);
CGContextTranslateCTM(effectInContext, 0, -self.size.height);
CGContextDrawImage(effectInContext, imageRect, self.CGImage);
vImage_Buffer effectInBuffer;
effectInBuffer.data = CGBitmapContextGetData(effectInContext);
effectInBuffer.width = CGBitmapContextGetWidth(effectInContext);
effectInBuffer.height = CGBitmapContextGetHeight(effectInContext);
effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext);
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef effectOutContext = UIGraphicsGetCurrentContext();
vImage_Buffer effectOutBuffer;
effectOutBuffer.data = CGBitmapContextGetData(effectOutContext);
effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext);
effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext);
effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext);
if (hasBlur) {
// A description of how to compute the box kernel width from the Gaussian
// radius (aka standard deviation) appears in the SVG spec:
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
//
// For larger values of 's' (s >= 2.0), an approximation can be used: Three
// successive box-blurs build a piece-wise quadratic convolution kernel, which
// approximates the Gaussian kernel to within roughly 3%.
//
// let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale];
uint32_t radius = (uint32_t)floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5);
if (radius % 2 != 1) {
radius += 1; // force radius to be odd so that the three box-blur methodology works.
}
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
}
BOOL effectImageBuffersAreSwapped = NO;
if (hasSaturationChange) {
CGFloat s = saturationDeltaFactor;
CGFloat floatingPointSaturationMatrix[] = {
0.0722f + 0.9278f * s, 0.0722f - 0.0722f * s, 0.0722f - 0.0722f * s, 0,
0.7152f - 0.7152f * s, 0.7152f + 0.2848f * s, 0.7152f - 0.7152f * s, 0,
0.2126f - 0.2126f * s, 0.2126f - 0.2126f * s, 0.2126f + 0.7873f * s, 0,
0, 0, 0, 1,
};
const int32_t divisor = 256;
NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]);
int16_t saturationMatrix[matrixSize];
for (NSUInteger i = 0; i < matrixSize; ++i) {
saturationMatrix[i] = (int16_t)round(floatingPointSaturationMatrix[i] * divisor);
}
if (hasBlur) {
vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
effectImageBuffersAreSwapped = YES;
}
else {
vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
}
}
if (!effectImageBuffersAreSwapped)
effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (effectImageBuffersAreSwapped)
effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
// Set up output context.
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef outputContext = UIGraphicsGetCurrentContext();
CGContextScaleCTM(outputContext, 1.0, -1.0);
CGContextTranslateCTM(outputContext, 0, -self.size.height);
// Draw base image.
CGContextDrawImage(outputContext, imageRect, self.CGImage);
// Draw effect image.
if (hasBlur) {
CGContextSaveGState(outputContext);
if (maskImage) {
CGContextClipToMask(outputContext, imageRect, maskImage.CGImage);
}
CGContextDrawImage(outputContext, imageRect, effectImage.CGImage);
CGContextRestoreGState(outputContext);
}
// Add in color tint.
if (tintColor) {
CGContextSaveGState(outputContext);
CGContextSetFillColorWithColor(outputContext, tintColor.CGColor);
CGContextFillRect(outputContext, imageRect);
CGContextRestoreGState(outputContext);
}
// Output image is ready.
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
@end

Просмотреть файл

@ -0,0 +1,18 @@
//
// UIWindow+AHKAdditions.h
// AHKActionSheetExample
//
// Created by Arkadiusz on 14-04-14.
// Copyright (c) 2014 Arkadiusz Holko. All rights reserved.
//
// Original source: https://github.com/Sumi-Interactive/SIAlertView/blob/master/SIAlertView/UIWindow%2BSIUtils.h
#import <UIKit/UIKit.h>
@interface UIWindow (AHKAdditions)
- (UIViewController *)ahk_viewControllerForStatusBarStyle;
- (UIViewController *)ahk_viewControllerForStatusBarHidden;
- (UIImage *)ahk_snapshot;
@end

Просмотреть файл

@ -0,0 +1,98 @@
//
// UIWindow+AHKAdditions.m
// AHKActionSheetExample
//
// Created by Arkadiusz on 14-04-14.
// Copyright (c) 2014 Arkadiusz Holko. All rights reserved.
//
#import "UIWindow+AHKAdditions.h"
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
@implementation UIWindow (AHKAdditions)
#pragma mark - Public
- (UIViewController *)ahk_viewControllerForStatusBarStyle
{
UIViewController *currentViewController = [self currentViewController];
while ([currentViewController childViewControllerForStatusBarStyle]) {
currentViewController = [currentViewController childViewControllerForStatusBarStyle];
}
return currentViewController;
}
- (UIViewController *)ahk_viewControllerForStatusBarHidden
{
UIViewController *currentViewController = [self currentViewController];
while ([currentViewController childViewControllerForStatusBarHidden]) {
currentViewController = [currentViewController childViewControllerForStatusBarHidden];
}
return currentViewController;
}
- (UIImage *)ahk_snapshot
{
// source (under MIT license): https://github.com/shinydevelopment/SDScreenshotCapture/blob/master/SDScreenshotCapture/SDScreenshotCapture.m#L35
// UIWindow doesn't have to be rotated on iOS 8+.
BOOL ignoreOrientation = SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0");
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
CGSize imageSize = CGSizeZero;
if (UIInterfaceOrientationIsPortrait(orientation) || ignoreOrientation) {
imageSize = [UIScreen mainScreen].bounds.size;
} else {
imageSize = CGSizeMake([UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width);
}
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, self.center.x, self.center.y);
CGContextConcatCTM(context, self.transform);
CGContextTranslateCTM(context, -self.bounds.size.width * self.layer.anchorPoint.x, -self.bounds.size.height * self.layer.anchorPoint.y);
// correct for the screen orientation
if (!ignoreOrientation) {
if (orientation == UIInterfaceOrientationLandscapeLeft) {
CGContextRotateCTM(context, (CGFloat)M_PI_2);
CGContextTranslateCTM(context, 0, -imageSize.width);
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
CGContextRotateCTM(context, (CGFloat)-M_PI_2);
CGContextTranslateCTM(context, -imageSize.height, 0);
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
CGContextRotateCTM(context, (CGFloat)M_PI);
CGContextTranslateCTM(context, -imageSize.width, -imageSize.height);
}
}
if([self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
} else {
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
}
CGContextRestoreGState(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
#pragma mark - Private
- (UIViewController *)currentViewController
{
UIViewController *viewController = self.rootViewController;
while (viewController.presentedViewController) {
viewController = viewController.presentedViewController;
}
return viewController;
}
@end

Просмотреть файл

@ -0,0 +1,26 @@
//
// BKPasscodeDummyViewController.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 8. 3..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol BKPasscodeDummyViewControllerDelegate;
@interface BKPasscodeDummyViewController : UIViewController
@property (nonatomic, weak) id<BKPasscodeDummyViewControllerDelegate> delegate;
@end
@protocol BKPasscodeDummyViewControllerDelegate <NSObject>
- (void)dummyViewControllerWillAppear:(BKPasscodeDummyViewController *)aViewController;
- (void)dummyViewControllerDidAppear:(BKPasscodeDummyViewController *)aViewController;
@end

Просмотреть файл

@ -0,0 +1,41 @@
//
// BKPasscodeDummyViewController.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 8. 3..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKPasscodeDummyViewController.h"
@interface BKPasscodeDummyViewController ()
@end
@implementation BKPasscodeDummyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.delegate dummyViewControllerWillAppear:self];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.presentedViewController == nil) {
// only calls delegate when presented view controller(modal view controller) does not exists.
[self.delegate dummyViewControllerDidAppear:self];
}
}
@end

Просмотреть файл

@ -0,0 +1,63 @@
//
// BKPasscodeField.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 4. 20..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol BKPasscodeFieldDelegate;
@protocol BKPasscodeFieldImageSource;
@interface BKPasscodeField : UIControl <UIKeyInput>
// delegate
@property (nonatomic, weak) id<BKPasscodeFieldDelegate> delegate;
@property (nonatomic, weak) id<BKPasscodeFieldImageSource> imageSource;
// passcode
@property (nonatomic, strong) NSString *passcode;
// configurations
@property (nonatomic) NSUInteger maximumLength;
@property (nonatomic) CGSize dotSize;
@property (nonatomic) CGFloat lineHeight;
@property (nonatomic) CGFloat dotSpacing;
@property (nonatomic, strong) UIColor *dotColor;
@property (nonatomic) UIKeyboardType keyboardType;
@end
@protocol BKPasscodeFieldDelegate <NSObject>
@optional
/**
* Ask the delegate that whether passcode field accepts text.
* If you want to accept entering text, return YES.
*/
- (BOOL)passcodeField:(BKPasscodeField *)aPasscodeField shouldInsertText:(NSString *)aText;
/**
* Ask the delegate that whether passcode can be deleted.
* If you want to accept deleting passcode, return YES.
*/
- (BOOL)passcodeFieldShouldDeleteBackward:(BKPasscodeField *)aPasscodeField;
@end
@protocol BKPasscodeFieldImageSource <NSObject>
@optional
/**
* Ask the image source for a image to display passcode digit at index.
* If you don't implement this, default shape (line for blank digit and circule for filled digit) will be displayed.
*/
- (UIImage *)passcodeField:(BKPasscodeField *)aPasscodeField dotImageAtIndex:(NSInteger)aIndex filled:(BOOL)aFilled;
@end

Просмотреть файл

@ -0,0 +1,271 @@
//
// BKPasscodeField.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 4. 20..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKPasscodeField.h"
@interface BKPasscodeField ()
@property (strong, nonatomic) NSMutableString *mutablePasscode;
@property (strong, nonatomic) NSRegularExpression *nonDigitRegularExpression;
@end
@implementation BKPasscodeField
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _initialize];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self _initialize];
}
return self;
}
- (id)init
{
self = [super init];
if (self) {
[self _initialize];
}
return self;
}
- (void)_initialize
{
_maximumLength = 4;
_dotSize = CGSizeMake(18.0f, 19.0f);
_dotSpacing = 25.0f;
_lineHeight = 3.0f;
_dotColor = [UIColor blackColor];
self.backgroundColor = [UIColor clearColor];
_mutablePasscode = [[NSMutableString alloc] initWithCapacity:4];
[self addTarget:self action:@selector(didTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
}
- (NSRegularExpression *)nonDigitRegularExpression
{
if (nil == _nonDigitRegularExpression) {
_nonDigitRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"[^0-9]+" options:0 error:nil];
}
return _nonDigitRegularExpression;
}
- (NSString *)passcode
{
return self.mutablePasscode;
}
- (void)setPasscode:(NSString *)passcode
{
if (passcode) {
if (passcode.length > self.maximumLength) {
passcode = [passcode substringWithRange:NSMakeRange(0, self.maximumLength)];
}
self.mutablePasscode = [NSMutableString stringWithString:passcode];
} else {
self.mutablePasscode = [NSMutableString string];
}
[self setNeedsDisplay];
}
#pragma mark - UIKeyInput
- (BOOL)hasText
{
return (self.mutablePasscode.length > 0);
}
- (void)insertText:(NSString *)text
{
if (self.enabled == NO) {
return;
}
if (self.keyboardType == UIKeyboardTypeNumberPad) {
text = [self.nonDigitRegularExpression stringByReplacingMatchesInString:text options:0 range:NSMakeRange(0, text.length) withTemplate:@""];
}
if (text.length == 0) {
return;
}
NSInteger newLength = self.mutablePasscode.length + text.length;
if (newLength > self.maximumLength) {
return;
}
if ([self.delegate respondsToSelector:@selector(passcodeField:shouldInsertText:)]) {
if (NO == [self.delegate passcodeField:self shouldInsertText:text]) {
return;
}
}
[self.mutablePasscode appendString:text];
[self setNeedsDisplay];
[self sendActionsForControlEvents:UIControlEventEditingChanged];
}
- (void)deleteBackward
{
if (self.enabled == NO) {
return;
}
if ([self.delegate respondsToSelector:@selector(passcodeFieldShouldDeleteBackward:)]) {
if (NO == [self.delegate passcodeFieldShouldDeleteBackward:self]) {
return;
}
}
if (self.mutablePasscode.length == 0) {
return;
}
[self.mutablePasscode deleteCharactersInRange:NSMakeRange(self.mutablePasscode.length - 1, 1)];
[self setNeedsDisplay];
[self sendActionsForControlEvents:UIControlEventEditingChanged];
}
- (UITextAutocapitalizationType)autocapitalizationType
{
return UITextAutocapitalizationTypeNone;
}
- (UITextAutocorrectionType)autocorrectionType
{
return UITextAutocorrectionTypeNo;
}
- (UITextSpellCheckingType)spellCheckingType
{
return UITextSpellCheckingTypeNo;
}
- (BOOL)enablesReturnKeyAutomatically
{
return YES;
}
- (UIKeyboardAppearance)keyboardAppearance
{
return UIKeyboardAppearanceDefault;
}
- (UIReturnKeyType)returnKeyType
{
return UIReturnKeyDone;
}
- (BOOL)isSecureTextEntry
{
return YES;
}
#pragma mark - UIView
- (CGSize)contentSize
{
return CGSizeMake(self.maximumLength * _dotSize.width + (self.maximumLength - 1) * _dotSpacing,
_dotSize.height);
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGSize contentSize = [self contentSize];
CGPoint origin = CGPointMake(floorf((self.frame.size.width - contentSize.width) * 0.5f),
floorf((self.frame.size.height - contentSize.height) * 0.5f));
if ([self.imageSource respondsToSelector:@selector(passcodeField:dotImageAtIndex:filled:)]) {
for (NSUInteger i = 0; i < self.maximumLength; i++) {
UIImage *image = nil;
if (i < self.mutablePasscode.length) {
// draw filled image
image = [self.imageSource passcodeField:self dotImageAtIndex:i filled:YES];
} else {
// draw blank image
image = [self.imageSource passcodeField:self dotImageAtIndex:i filled:NO];
}
if (image) {
CGRect imageFrame = CGRectMake(origin.x, origin.y, self.dotSize.width, self.dotSize.height);
[image drawInRect:imageFrame];
}
origin.x += (self.dotSize.width + self.dotSpacing);
}
} else {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.dotColor.CGColor);
for (NSUInteger i = 0; i < self.maximumLength; i++) {
if (i < self.mutablePasscode.length) {
// draw circle
CGRect circleFrame = CGRectMake(origin.x, origin.y, self.dotSize.width, self.dotSize.height);
CGContextFillEllipseInRect(context, circleFrame);
} else {
// draw line
CGRect lineFrame = CGRectMake(origin.x, origin.y + floorf((self.dotSize.height - self.lineHeight) * 0.5f),
self.dotSize.width, self.lineHeight);
CGContextFillRect(context, lineFrame);
}
origin.x += (self.dotSize.width + self.dotSpacing);
}
}
}
- (CGSize)sizeThatFits:(CGSize)size
{
return [self contentSize];
}
#pragma mark - UIResponder
- (BOOL)canBecomeFirstResponder
{
return YES;
}
#pragma mark - Actions
- (void)didTouchUpInside:(id)sender
{
[self becomeFirstResponder];
}
@end

Просмотреть файл

@ -0,0 +1,53 @@
//
// BKPasscodeInputView.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 4. 20..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "BKPasscodeField.h"
typedef enum : NSUInteger {
BKPasscodeInputViewNumericPasscodeStyle,
BKPasscodeInputViewNormalPasscodeStyle,
} BKPasscodeInputViewPasscodeStyle;
@protocol BKPasscodeInputViewDelegate;
@interface BKPasscodeInputView : UIView <UITextFieldDelegate, BKPasscodeFieldDelegate, NSCopying>
@property (nonatomic, weak) id<BKPasscodeInputViewDelegate> delegate;
@property (nonatomic) BKPasscodeInputViewPasscodeStyle passcodeStyle;
@property (nonatomic) UIKeyboardType keyboardType;
@property (nonatomic) NSUInteger maximumLength;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *message;
@property (nonatomic, strong) NSString *errorMessage;
@property (nonatomic, getter = isEnabled) BOOL enabled;
@property (nonatomic, strong) NSString *passcode;
@property (nonatomic, strong, readonly) UIControl *passcodeField;
// You can override these methods to customize message label appearance.
+ (void)configureTitleLabel:(UILabel *)aLabel;
+ (void)configureMessageLabel:(UILabel *)aLabel;
+ (void)configureErrorMessageLabel:(UILabel *)aLabel;
@end
@protocol BKPasscodeInputViewDelegate <NSObject>
/**
* Tells the delegate that maximum length of passcode is entered or user tapped Done button in the keyboard (in case of BKPasscodeInputViewNormalPasscodeStyle).
*/
- (void)passcodeInputViewDidFinish:(BKPasscodeInputView *)aInputView;
@end

Просмотреть файл

@ -0,0 +1,434 @@
//
// BKPasscodeInputView.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 4. 20..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKPasscodeInputView.h"
#define kLabelPasscodeSpacePortrait (30.0f)
#define kLabelPasscodeSpaceLandscape (10.0f)
#define kTextLeftRightSpace (20.0f)
#define kErrorMessageLeftRightPadding (10.0f)
#define kErrorMessageTopBottomPadding (5.0f)
#define kDefaultNumericPasscodeMaximumLength (4)
#define kDefaultNormalPasscodeMaximumLength (20)
@interface BKPasscodeInputView () {
BOOL _isKeyboardTypeSet;
}
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *messageLabel;
@property (nonatomic, strong) UILabel *errorMessageLabel;
@property (nonatomic, strong) UIControl *passcodeField;
@end
@implementation BKPasscodeInputView
@synthesize maximumLength = _maximumLength;
@synthesize keyboardType = _keyboardType;
@synthesize passcodeField = _passcodeField;
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _initialize];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self _initialize];
}
return self;
}
- (void)_initialize
{
self.backgroundColor = [UIColor clearColor];
_enabled = YES;
_passcodeStyle = BKPasscodeInputViewNumericPasscodeStyle;
_keyboardType = UIKeyboardTypeNumberPad;
_maximumLength = 0;
_titleLabel = [[UILabel alloc] init];
[[self class] configureTitleLabel:_titleLabel];
[self addSubview:_titleLabel];
_messageLabel = [[UILabel alloc] init];
[[self class] configureMessageLabel:_messageLabel];
[self addSubview:_messageLabel];
_errorMessageLabel = [[UILabel alloc] init];
[[self class] configureErrorMessageLabel:_errorMessageLabel];
_errorMessageLabel.hidden = YES;
[self addSubview:_errorMessageLabel];
}
+ (void)configureTitleLabel:(UILabel *)aLabel
{
aLabel.backgroundColor = [UIColor clearColor];
aLabel.numberOfLines = 1;
aLabel.textAlignment = NSTextAlignmentCenter;
aLabel.lineBreakMode = NSLineBreakByTruncatingTail;
aLabel.font = [UIFont boldSystemFontOfSize:15.0f];
}
+ (void)configureMessageLabel:(UILabel *)aLabel
{
aLabel.backgroundColor = [UIColor clearColor];
aLabel.numberOfLines = 0;
aLabel.textAlignment = NSTextAlignmentCenter;
aLabel.lineBreakMode = NSLineBreakByWordWrapping;
aLabel.font = [UIFont systemFontOfSize:15.0f];
}
+ (void)configureErrorMessageLabel:(UILabel *)aLabel
{
aLabel.backgroundColor = [UIColor clearColor];
aLabel.numberOfLines = 0;
aLabel.textAlignment = NSTextAlignmentCenter;
aLabel.lineBreakMode = NSLineBreakByWordWrapping;
aLabel.backgroundColor = [UIColor colorWithRed:0.63 green:0.2 blue:0.13 alpha:1];
aLabel.textColor = [UIColor whiteColor];
aLabel.font = [UIFont systemFontOfSize:15.0f];
aLabel.layer.cornerRadius = 10.0f;
aLabel.layer.masksToBounds = YES;
}
- (void)setPasscodeStyle:(BKPasscodeInputViewPasscodeStyle)passcodeStyle
{
if (_passcodeStyle != passcodeStyle) {
_passcodeStyle = passcodeStyle;
if (_passcodeField) {
_passcodeField = nil;
[self passcodeField]; // load passcode field immediately if already exists before.
}
}
}
- (UIControl *)passcodeField
{
if (nil == _passcodeField) {
switch (_passcodeStyle) {
case BKPasscodeInputViewNumericPasscodeStyle:
{
if (_maximumLength == 0) {
_maximumLength = kDefaultNumericPasscodeMaximumLength;
}
if (NO == _isKeyboardTypeSet) {
_keyboardType = UIKeyboardTypeNumberPad;
}
BKPasscodeField *passcodeField = [[BKPasscodeField alloc] init];
passcodeField.delegate = self;
passcodeField.keyboardType = _keyboardType;
passcodeField.maximumLength = _maximumLength;
[passcodeField addTarget:self action:@selector(passcodeControlEditingChanged:) forControlEvents:UIControlEventEditingChanged];
[self setPasscodeField:passcodeField];
break;
}
case BKPasscodeInputViewNormalPasscodeStyle:
{
if (_maximumLength == 0) {
_maximumLength = kDefaultNormalPasscodeMaximumLength;
}
if (NO == _isKeyboardTypeSet) {
_keyboardType = UIKeyboardTypeASCIICapable;
}
UITextField *textField = [[UITextField alloc] init];
textField.delegate = self;
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
textField.spellCheckingType = UITextSpellCheckingTypeNo;
textField.enablesReturnKeyAutomatically = YES;
textField.keyboardType = _keyboardType;
textField.secureTextEntry = YES;
textField.font = [UIFont systemFontOfSize:25.0f];
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.returnKeyType = UIReturnKeyDone;
[self setPasscodeField:textField];
break;
}
}
}
return _passcodeField;
}
- (void)setPasscodeField:(UIControl *)passcodeField
{
if (_passcodeField != passcodeField) {
[_passcodeField removeFromSuperview];
_passcodeField = passcodeField;
if (_passcodeField) {
[self addSubview:_passcodeField];
}
[self setNeedsLayout];
}
}
- (void)setMaximumLength:(NSUInteger)maximumLength
{
_maximumLength = maximumLength;
if ([_passcodeField isKindOfClass:[BKPasscodeField class]]) {
[(BKPasscodeField *)_passcodeField setMaximumLength:maximumLength];
}
}
- (void)setKeyboardType:(UIKeyboardType)keyboardType
{
_isKeyboardTypeSet = YES;
_keyboardType = keyboardType;
[(id<UITextInputTraits>)_passcodeField setKeyboardType:keyboardType];
}
- (void)setTitle:(NSString *)title
{
self.titleLabel.text = title;
[self setNeedsLayout];
}
- (NSString *)title
{
return self.titleLabel.text;
}
- (void)setMessage:(NSString *)message
{
self.messageLabel.text = message;
self.messageLabel.hidden = NO;
self.errorMessageLabel.text = nil;
self.errorMessageLabel.hidden = YES;
[self setNeedsLayout];
}
- (NSString *)message
{
return self.messageLabel.text;
}
- (void)setErrorMessage:(NSString *)errorMessage
{
self.errorMessageLabel.text = errorMessage;
self.errorMessageLabel.hidden = NO;
self.messageLabel.text = nil;
self.messageLabel.hidden = YES;
[self setNeedsLayout];
}
- (NSString *)errorMessage
{
return self.errorMessageLabel.text;
}
- (NSString *)passcode
{
switch (self.passcodeStyle) {
case BKPasscodeInputViewNumericPasscodeStyle:
return [(BKPasscodeField *)self.passcodeField passcode];
case BKPasscodeInputViewNormalPasscodeStyle:
return [(UITextField *)self.passcodeField text];
}
}
- (void)setPasscode:(NSString *)passcode
{
switch (self.passcodeStyle) {
case BKPasscodeInputViewNumericPasscodeStyle:
[(BKPasscodeField *)self.passcodeField setPasscode:passcode];
break;
case BKPasscodeInputViewNormalPasscodeStyle:
[(UITextField *)self.passcodeField setText:passcode];
break;
}
}
#pragma mark - UIView
- (CGFloat)labelPasscodeSpace
{
#ifdef SHARE_IN
return (self.frame.size.width < self.frame.size.height) ? kLabelPasscodeSpacePortrait : kLabelPasscodeSpaceLandscape;
#else
return UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ? kLabelPasscodeSpacePortrait : kLabelPasscodeSpaceLandscape;
#endif
}
- (void)layoutSubviews
{
[super layoutSubviews];
// layout passcode control to center
[self.passcodeField sizeToFit];
if ([self.passcodeField isKindOfClass:[UITextField class]]) {
self.passcodeField.frame = CGRectMake(0, 0, self.frame.size.width - kTextLeftRightSpace * 2.0f, CGRectGetHeight(self.passcodeField.frame) + 10.0f);
}
self.passcodeField.center = CGPointMake(CGRectGetWidth(self.frame) * 0.5f, CGRectGetHeight(self.frame) * 0.5f);
CGFloat maxTextWidth = self.frame.size.width - (kTextLeftRightSpace * 2.0f);
CGFloat labelPasscodeSpace = [self labelPasscodeSpace];
// layout title label
_titleLabel.frame = CGRectMake(kTextLeftRightSpace, 0, maxTextWidth, self.frame.size.height);
[_titleLabel sizeToFit];
CGRect rect = _titleLabel.frame;
rect.origin.x = floorf((self.frame.size.width - CGRectGetWidth(rect)) * 0.5f);
rect.origin.y = CGRectGetMinY(self.passcodeField.frame) - labelPasscodeSpace - CGRectGetHeight(_titleLabel.frame);
_titleLabel.frame = rect;
// layout message label
if (!_messageLabel.hidden) {
_messageLabel.frame = CGRectMake(kTextLeftRightSpace, CGRectGetMaxY(self.passcodeField.frame) + labelPasscodeSpace, maxTextWidth, self.frame.size.height);
[_messageLabel sizeToFit];
rect = _messageLabel.frame;
rect.origin.x = floorf((self.frame.size.width - CGRectGetWidth(rect)) * 0.5f);
_messageLabel.frame = rect;
}
// layout error message label
if (!_errorMessageLabel.hidden) {
_errorMessageLabel.frame = CGRectMake(0, CGRectGetMaxY(self.passcodeField.frame) + labelPasscodeSpace,
maxTextWidth - kErrorMessageLeftRightPadding * 2.0f,
self.frame.size.height);
[_errorMessageLabel sizeToFit];
rect = _errorMessageLabel.frame;
rect.size.width += (kErrorMessageLeftRightPadding * 2.0f);
rect.size.height += (kErrorMessageTopBottomPadding * 2.0f);
rect.origin.x = floorf((self.frame.size.width - rect.size.width) * 0.5f);
_errorMessageLabel.frame = rect;
}
}
#pragma mark - UIResponder
- (BOOL)canBecomeFirstResponder
{
return [self.passcodeField canBecomeFirstResponder];
}
- (BOOL)becomeFirstResponder
{
return [self.passcodeField becomeFirstResponder];
}
- (BOOL)canResignFirstResponder
{
return [self.passcodeField canResignFirstResponder];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self.passcodeField becomeFirstResponder];
}
#pragma mark - Actions
- (void)passcodeControlEditingChanged:(id)sender
{
if (![self.passcodeField isKindOfClass:[BKPasscodeField class]]) {
return;
}
BKPasscodeField *passcodeField = (BKPasscodeField *)self.passcodeField;
if (passcodeField.passcode.length == passcodeField.maximumLength) {
if ([self.delegate respondsToSelector:@selector(passcodeInputViewDidFinish:)]) {
[self.delegate passcodeInputViewDidFinish:self];
}
}
}
#pragma mark - BKPasscodeFieldDelegate
- (BOOL)passcodeField:(BKPasscodeField *)aPasscodeField shouldInsertText:(NSString *)aText
{
return self.isEnabled;
}
- (BOOL)passcodeFieldShouldDeleteBackward:(BKPasscodeField *)aPasscodeField
{
return self.isEnabled;
}
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (self.isEnabled == NO) {
return NO;
}
NSUInteger length = textField.text.length - range.length + string.length;
if (length > self.maximumLength) {
return NO;
}
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (self.isEnabled == NO) {
return NO;
}
if ([self.delegate respondsToSelector:@selector(passcodeInputViewDidFinish:)]) {
[self.delegate passcodeInputViewDidFinish:self];
return NO;
} else {
return YES; // default behavior
}
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
BKPasscodeInputView *view = [[[self class] alloc] initWithFrame:self.bounds];
view.delegate = self.delegate;
view.autoresizingMask = self.autoresizingMask;
view.passcodeStyle = self.passcodeStyle;
view.keyboardType = self.keyboardType;
view.maximumLength = self.maximumLength;
return view;
}
@end

Просмотреть файл

@ -0,0 +1,53 @@
//
// BKPasscodeLockScreenManager.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 8. 2..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BKPasscodeViewController.h"
#import "BKPasscodeDummyViewController.h"
@protocol BKPasscodeLockScreenManagerDelegate;
@interface BKPasscodeLockScreenManager : NSObject <BKPasscodeDummyViewControllerDelegate>
@property (weak, nonatomic) id<BKPasscodeLockScreenManagerDelegate> delegate;
/**
* Shared(singleton) instance.
*/
+ (BKPasscodeLockScreenManager *)sharedManager;
/**
* Shows lock screen. You should call this method at applicationDidEnterBackground: in app delegate.
*/
- (void)showLockScreen:(BOOL)animated;
@end
@protocol BKPasscodeLockScreenManagerDelegate <NSObject>
/**
* Ask the delegate a view controller that should be displayed as lock screen.
*/
- (UIViewController *)lockScreenManagerPasscodeViewController:(BKPasscodeLockScreenManager *)aManager;
@optional
/**
* Ask the delegate that lock screen should be displayed or not.
* If you prevent displaying lock screen, return NO.
* If delegate does not implement this method, the lock screen will be shown everytime when application did enter background.
*/
- (BOOL)lockScreenManagerShouldShowLockScreen:(BKPasscodeLockScreenManager *)aManager;
/**
* Ask the delegate for the view that will be used as snapshot.
*/
- (UIView *)lockScreenManagerBlindView:(BKPasscodeLockScreenManager *)aManager;
@end

Просмотреть файл

@ -0,0 +1,120 @@
//
// BKPasscodeLockScreenManager.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 8. 2..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKPasscodeLockScreenManager.h"
#import "BKPasscodeViewController.h"
static BKPasscodeLockScreenManager *_sharedManager;
@interface BKPasscodeLockScreenManager ()
@property (strong, nonatomic) UIWindow *mainWindow;
@property (strong, nonatomic) UIWindow *lockScreenWindow;
@property (strong, nonatomic) UIView *blindView;
@end
@implementation BKPasscodeLockScreenManager
+ (BKPasscodeLockScreenManager *)sharedManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedManager = [[BKPasscodeLockScreenManager alloc] init];
});
return _sharedManager;
}
- (void)showLockScreen:(BOOL)animated
{
NSAssert(self.delegate, @"delegate is not assigned.");
if (self.lockScreenWindow && self.lockScreenWindow.rootViewController) {
return;
}
if ([self.delegate respondsToSelector:@selector(lockScreenManagerShouldShowLockScreen:)]) {
if (NO == [self.delegate lockScreenManagerShouldShowLockScreen:self]) {
return;
}
}
// get the main window
#ifndef SHARE_IN
self.mainWindow = [[UIApplication sharedApplication] keyWindow];
// dismiss keyboard before showing lock screen
[self.mainWindow.rootViewController.view endEditing:YES];
#endif
// add blind view
UIView *blindView;
if ([self.delegate respondsToSelector:@selector(lockScreenManagerBlindView:)]) {
blindView = [self.delegate lockScreenManagerBlindView:self];
}
if (nil == blindView) {
blindView = [[UIView alloc] init];
blindView.backgroundColor = [UIColor whiteColor];
}
blindView.frame = self.mainWindow.bounds;
blindView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.mainWindow addSubview:blindView];
self.blindView = blindView;
// set dummy view controller as root view controller
BKPasscodeDummyViewController *dummyViewController = [[BKPasscodeDummyViewController alloc] init];
UIWindow *lockScreenWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
lockScreenWindow.windowLevel = self.mainWindow.windowLevel + 1;
lockScreenWindow.rootViewController = dummyViewController;
lockScreenWindow.backgroundColor = [UIColor clearColor];
[lockScreenWindow makeKeyAndVisible];
// present lock screen
UIViewController *lockScreenViewController = [self.delegate lockScreenManagerPasscodeViewController:self];
if (animated) {
blindView.hidden = YES;
}
[lockScreenWindow.rootViewController presentViewController:lockScreenViewController animated:animated completion:^{
blindView.hidden = NO;
}];
self.lockScreenWindow = lockScreenWindow;
[lockScreenViewController.view.superview bringSubviewToFront:lockScreenViewController.view];
dummyViewController.delegate = self;
}
- (void)dummyViewControllerWillAppear:(BKPasscodeDummyViewController *)aViewController
{
// remove blind view
[self.blindView removeFromSuperview];
self.blindView = nil;
}
- (void)dummyViewControllerDidAppear:(BKPasscodeDummyViewController *)aViewController
{
if ([UIView instancesRespondToSelector:@selector(tintColor)]) {
self.lockScreenWindow = nil;
} else {
[self performSelector:@selector(setLockScreenWindow:) withObject:nil afterDelay:0.1f]; // workaround for wired dealloc on iOS 6
}
[self.mainWindow makeKeyAndVisible];
self.mainWindow = nil;
}
@end

Просмотреть файл

@ -0,0 +1,17 @@
//
// BKPasscodeUtils.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 10. 4..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
/*
* System Versioning Preprocessor Macros
*/
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

Просмотреть файл

@ -0,0 +1,98 @@
//
// BKPasscodeViewController.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 4. 20..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "BKPasscodeInputView.h"
#import "BKTouchIDSwitchView.h"
#import "BKTouchIDManager.h"
typedef enum : NSUInteger {
BKPasscodeViewControllerNewPasscodeType,
BKPasscodeViewControllerChangePasscodeType,
BKPasscodeViewControllerCheckPasscodeType
} BKPasscodeViewControllerType;
@protocol BKPasscodeViewControllerDelegate;
@interface BKPasscodeViewController : UIViewController <BKPasscodeInputViewDelegate, BKTouchIDSwitchViewDelegate>
@property (nonatomic, weak) id<BKPasscodeViewControllerDelegate> delegate;
@property (nonatomic) BKPasscodeViewControllerType type;
@property (nonatomic) BKPasscodeInputViewPasscodeStyle passcodeStyle;
@property (nonatomic) UIKeyboardType keyboardType;
@property (nonatomic, strong, readonly) BKPasscodeInputView *passcodeInputView;
@property (nonatomic, strong) BKTouchIDManager *touchIDManager;
@property BOOL inputViewTitlePassword;
/**
* Customize passcode input view
* You may override to customize passcode input view appearance.
*/
- (void)customizePasscodeInputView:(BKPasscodeInputView *)aPasscodeInputView;
/**
* Instantiate passcode input view.
* You may override to use custom passcode input view.
*/
- (BKPasscodeInputView *)instantiatePasscodeInputView;
/**
* Prompts Touch ID view to scan fingerprint.
*/
- (void)startTouchIDAuthenticationIfPossible;
/**
* Prompts Touch ID view to scan fingerprint.
* If Touch ID is disabled or unavailable, value of 'prompted' will be NO.
*/
- (void)startTouchIDAuthenticationIfPossible:(void(^)(BOOL prompted))aCompletionBlock;
@end
@protocol BKPasscodeViewControllerDelegate <NSObject>
/**
* Tells the delegate that passcode is created or authenticated successfully.
*/
- (void)passcodeViewController:(BKPasscodeViewController *)aViewController didFinishWithPasscode:(NSString *)aPasscode;
@optional
/**
* Tells the delegate that Touch ID error occured.
*/
- (void)passcodeViewControllerDidFailTouchIDKeychainOperation:(BKPasscodeViewController *)aViewController;
/**
* Ask the delegate to verify that a passcode is correct. You must call the resultHandler with result.
* You can check passcode asynchronously and show progress view (e.g. UIActivityIndicator) in the view controller if authentication takes too long.
* You must call result handler in main thread.
*/
- (void)passcodeViewController:(BKPasscodeViewController *)aViewController authenticatePasscode:(NSString *)aPasscode resultHandler:(void(^)(BOOL succeed))aResultHandler;
/**
* Tells the delegate that user entered incorrect passcode.
* You should manage failed attempts yourself and it should be returned by -[BKPasscodeViewControllerDelegate passcodeViewControllerNumberOfFailedAttempts:] method.
*/
- (void)passcodeViewControllerDidFailAttempt:(BKPasscodeViewController *)aViewController;
/**
* Ask the delegate that how many times incorrect passcode entered to display failed attempt count.
*/
- (NSUInteger)passcodeViewControllerNumberOfFailedAttempts:(BKPasscodeViewController *)aViewController;
/**
* Ask the delegate that whether passcode view should lock or unlock.
* If you return nil, passcode view will unlock otherwise it will lock until the date.
*/
- (NSDate *)passcodeViewControllerLockUntilDate:(BKPasscodeViewController *)aViewController;
@end

Просмотреть файл

@ -0,0 +1,583 @@
//
// BKPasscodeViewController.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 4. 20..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKPasscodeViewController.h"
#import "BKShiftingView.h"
#import "AFViewShaker.h"
#import "BKPasscodeUtils.h"
typedef enum : NSUInteger {
BKPasscodeViewControllerStateUnknown,
BKPasscodeViewControllerStateCheckPassword,
BKPasscodeViewControllerStateInputPassword,
BKPasscodeViewControllerStateReinputPassword
} BKPasscodeViewControllerState;
#define kBKPasscodeOneMinuteInSeconds (60)
#define kBKPasscodeDefaultKeyboardHeight (216)
@interface BKPasscodeViewController ()
@property (nonatomic, strong) BKShiftingView *shiftingView;
@property (nonatomic) BKPasscodeViewControllerState currentState;
@property (nonatomic, strong) NSString *oldPasscode;
@property (nonatomic, strong) NSString *theNewPasscode;
@property (nonatomic, strong) NSTimer *lockStateUpdateTimer;
@property (nonatomic) CGFloat keyboardHeight;
@property (nonatomic, strong) AFViewShaker *viewShaker;
@property (nonatomic) BOOL promptingTouchID;
@end
@implementation BKPasscodeViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// init state
_type = BKPasscodeViewControllerNewPasscodeType;
_currentState = BKPasscodeViewControllerStateInputPassword;
// create shifting view
self.shiftingView = [[BKShiftingView alloc] init];
self.shiftingView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.shiftingView.currentView = [self instantiatePasscodeInputView];
// keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveKeyboardWillShowHideNotification:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveKeyboardWillShowHideNotification:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveApplicationWillEnterForegroundNotification:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
self.keyboardHeight = kBKPasscodeDefaultKeyboardHeight; // sometimes keyboard notification is not posted at all. so setting default value.
}
return self;
}
- (void)dealloc
{
[self.lockStateUpdateTimer invalidate];
self.lockStateUpdateTimer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setType:(BKPasscodeViewControllerType)type
{
if (_type == type) {
return;
}
_type = type;
switch (type) {
case BKPasscodeViewControllerNewPasscodeType:
self.currentState = BKPasscodeViewControllerStateInputPassword;
break;
default:
self.currentState = BKPasscodeViewControllerStateCheckPassword;
break;
}
}
- (BKPasscodeInputView *)passcodeInputView
{
if (NO == [self.shiftingView.currentView isKindOfClass:[BKPasscodeInputView class]]) {
return nil;
}
return (BKPasscodeInputView *)self.shiftingView.currentView;
}
- (BKPasscodeInputView *)instantiatePasscodeInputView
{
BKPasscodeInputView *view = [[BKPasscodeInputView alloc] init];
view.delegate = self;
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
return view;
}
- (void)customizePasscodeInputView:(BKPasscodeInputView *)aPasscodeInputView
{
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor colorWithRed:0.94 green:0.94 blue:0.96 alpha:1]];
[self updatePasscodeInputViewTitle:self.passcodeInputView];
[self customizePasscodeInputView:self.passcodeInputView];
[self.view addSubview:self.shiftingView];
[self lockIfNeeded];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.passcodeInputView.isEnabled) {
//TWS
[self performSelector:@selector(startTouchIDAuthenticationIfPossible) withObject:nil afterDelay:0.2];
}
[self.passcodeInputView becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.view endEditing:YES];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
CGRect frame = self.view.bounds;
CGFloat topBarOffset = 0;
if ([self respondsToSelector:@selector(topLayoutGuide)]) {
topBarOffset = [self.topLayoutGuide length];
}
frame.origin.y += topBarOffset;
frame.size.height -= (topBarOffset + self.keyboardHeight);
self.shiftingView.frame = frame;
}
#pragma mark - Public methods
- (void)setPasscodeStyle:(BKPasscodeInputViewPasscodeStyle)passcodeStyle
{
self.passcodeInputView.passcodeStyle = passcodeStyle;
}
- (BKPasscodeInputViewPasscodeStyle)passcodeStyle
{
return self.passcodeInputView.passcodeStyle;
}
- (void)setKeyboardType:(UIKeyboardType)keyboardType
{
self.passcodeInputView.keyboardType = keyboardType;
}
- (UIKeyboardType)keyboardType
{
return self.passcodeInputView.keyboardType;
}
- (void)showLockMessageWithLockUntilDate:(NSDate *)lockUntil
{
NSTimeInterval timeInterval = [lockUntil timeIntervalSinceNow];
NSUInteger minutes = ceilf(timeInterval / 60.0f);
BKPasscodeInputView *inputView = self.passcodeInputView;
inputView.enabled = NO;
if (minutes == 1) {
inputView.title = NSLocalizedStringFromTable(@"Try again in 1 minute", @"BKPasscodeView", @"1분 후에 다시 시도");
} else {
inputView.title = [NSString stringWithFormat:NSLocalizedStringFromTable(@"Try again in %d minutes", @"BKPasscodeView", @"%d분 후에 다시 시도"), minutes];
}
NSUInteger numberOfFailedAttempts = [self.delegate passcodeViewControllerNumberOfFailedAttempts:self];
[self showFailedAttemptsCount:numberOfFailedAttempts inputView:inputView];
if (self.lockStateUpdateTimer == nil) {
NSTimeInterval delay = timeInterval + kBKPasscodeOneMinuteInSeconds - (kBKPasscodeOneMinuteInSeconds * (NSTimeInterval)minutes);
self.lockStateUpdateTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:delay]
interval:60.f
target:self
selector:@selector(lockStateUpdateTimerFired:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.lockStateUpdateTimer forMode:NSDefaultRunLoopMode];
}
}
- (BOOL)lockIfNeeded
{
if (self.currentState != BKPasscodeViewControllerStateCheckPassword) {
return NO;
}
if (NO == [self.delegate respondsToSelector:@selector(passcodeViewControllerLockUntilDate:)]) {
return NO;
}
NSDate *lockUntil = [self.delegate passcodeViewControllerLockUntilDate:self];
if (lockUntil == nil || [lockUntil timeIntervalSinceNow] < 0) {
return NO;
}
[self showLockMessageWithLockUntilDate:lockUntil];
return YES;
}
- (void)updateLockMessageOrUnlockIfNeeded
{
if (self.currentState != BKPasscodeViewControllerStateCheckPassword) {
return;
}
if (NO == [self.delegate respondsToSelector:@selector(passcodeViewControllerLockUntilDate:)]) {
return;
}
BKPasscodeInputView *inputView = self.passcodeInputView;
NSDate *lockUntil = [self.delegate passcodeViewControllerLockUntilDate:self];
if (lockUntil == nil || [lockUntil timeIntervalSinceNow] < 0) {
// invalidate timer
[self.lockStateUpdateTimer invalidate];
self.lockStateUpdateTimer = nil;
[self updatePasscodeInputViewTitle:inputView];
inputView.enabled = YES;
} else {
[self showLockMessageWithLockUntilDate:lockUntil];
}
}
- (void)lockStateUpdateTimerFired:(NSTimer *)timer
{
[self updateLockMessageOrUnlockIfNeeded];
}
- (void)startTouchIDAuthenticationIfPossible
{
[self startTouchIDAuthenticationIfPossible:nil];
}
- (void)startTouchIDAuthenticationIfPossible:(void (^)(BOOL))aCompletionBlock
{
if (NO == [self canAuthenticateWithTouchID]) {
if (aCompletionBlock) {
aCompletionBlock(NO);
}
return;
}
self.promptingTouchID = YES;
[self.touchIDManager loadPasscodeWithCompletionBlock:^(NSString *passcode) {
self.promptingTouchID = NO;
if (passcode) {
self.passcodeInputView.passcode = passcode;
[self passcodeInputViewDidFinish:self.passcodeInputView];
}
if (aCompletionBlock) {
aCompletionBlock(YES);
}
}];
}
#pragma mark - Private methods
- (void)updatePasscodeInputViewTitle:(BKPasscodeInputView *)passcodeInputView
{
switch (self.currentState) {
case BKPasscodeViewControllerStateCheckPassword:
if (self.type == BKPasscodeViewControllerChangePasscodeType) {
if (self.inputViewTitlePassword) passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your old password", @"BKPasscodeView", @"Enter your old password");
else passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your old passcode", @"BKPasscodeView", @"기존 암호 입력");
} else {
if (self.inputViewTitlePassword) passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your password", @"BKPasscodeView", @"Enter your password");
else passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your passcode", @"BKPasscodeView", @"암호 입력");
}
break;
case BKPasscodeViewControllerStateInputPassword:
if (self.type == BKPasscodeViewControllerChangePasscodeType) {
if (self.inputViewTitlePassword) passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your new password", @"BKPasscodeView", @"Enter your new password");
else passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your new passcode", @"BKPasscodeView", @"새로운 암호 입력");
} else {
if (self.inputViewTitlePassword) passcodeInputView.title = NSLocalizedStringFromTable(@"Enter a password", @"BKPasscodeView", @"Enter a password");
else passcodeInputView.title = NSLocalizedStringFromTable(@"Enter a passcode", @"BKPasscodeView", @"암호 입력");
}
break;
case BKPasscodeViewControllerStateReinputPassword:
if (self.inputViewTitlePassword) passcodeInputView.title = NSLocalizedStringFromTable(@"Re-enter your password", @"BKPasscodeView", @"Re-enter your password");
else passcodeInputView.title = NSLocalizedStringFromTable(@"Re-enter your passcode", @"BKPasscodeView", @"암호 재입력");
break;
default:
break;
}
}
- (void)showFailedAttemptsCount:(NSUInteger)failCount inputView:(BKPasscodeInputView *)aInputView
{
if (failCount == 0) {
if (self.inputViewTitlePassword) aInputView.errorMessage = NSLocalizedStringFromTable(@"Invalid Password", @"BKPasscodeView", @"Invalid Password");
else aInputView.errorMessage = NSLocalizedStringFromTable(@"Invalid Passcode", @"BKPasscodeView", @"잘못된 암호");
} else if (failCount == 1) {
if (self.inputViewTitlePassword) aInputView.errorMessage = NSLocalizedStringFromTable(@"1 Failed Password Attempt", @"BKPasscodeView", @"1 Failed Password Attempt");
else aInputView.errorMessage = NSLocalizedStringFromTable(@"1 Failed Passcode Attempt", @"BKPasscodeView", @"1번의 암호 입력 시도 실패");
} else {
if (self.inputViewTitlePassword) aInputView.errorMessage = [NSString stringWithFormat:NSLocalizedStringFromTable(@"%d Failed Password Attempts", @"BKPasscodeView", @"%d Failed Password Attempts"), failCount];
else aInputView.errorMessage = [NSString stringWithFormat:NSLocalizedStringFromTable(@"%d Failed Passcode Attempts", @"BKPasscodeView", @"%d번의 암호 입력 시도 실패"), failCount];
}
}
- (void)showTouchIDSwitchView
{
BKTouchIDSwitchView *view = [[BKTouchIDSwitchView alloc] init];
view.delegate = self;
view.touchIDSwitch.on = self.touchIDManager.isTouchIDEnabled;
[self.shiftingView showView:view withDirection:BKShiftingDirectionForward];
}
- (BOOL)canAuthenticateWithTouchID
{
if (NO == [BKTouchIDManager canUseTouchID]) {
return NO;
}
if (self.type != BKPasscodeViewControllerCheckPasscodeType) {
return NO;
}
if (nil == self.touchIDManager || NO == self.touchIDManager.isTouchIDEnabled) {
return NO;
}
if (self.promptingTouchID) {
return NO;
}
#ifndef SHARE_IN
if ([UIApplication sharedApplication].applicationState == UIApplicationStateInactive) {
return NO;
}
#endif
return YES;
}
#pragma mark - BKPasscodeInputViewDelegate
- (void)passcodeInputViewDidFinish:(BKPasscodeInputView *)aInputView
{
NSString *passcode = aInputView.passcode;
switch (self.currentState) {
case BKPasscodeViewControllerStateCheckPassword:
{
NSAssert([self.delegate respondsToSelector:@selector(passcodeViewController:authenticatePasscode:resultHandler:)],
@"delegate must implement passcodeViewController:authenticatePasscode:resultHandler:");
[self.delegate passcodeViewController:self authenticatePasscode:passcode resultHandler:^(BOOL succeed) {
NSAssert([NSThread isMainThread], @"you must invoke result handler in main thread.");
if (succeed) {
if (self.type == BKPasscodeViewControllerChangePasscodeType) {
self.oldPasscode = passcode;
self.currentState = BKPasscodeViewControllerStateInputPassword;
BKPasscodeInputView *newPasscodeInputView = [self.passcodeInputView copy];
[self customizePasscodeInputView:newPasscodeInputView];
[self updatePasscodeInputViewTitle:newPasscodeInputView];
[self.shiftingView showView:newPasscodeInputView withDirection:BKShiftingDirectionForward];
[self.passcodeInputView becomeFirstResponder];
} else {
[self.delegate passcodeViewController:self didFinishWithPasscode:passcode];
}
} else {
if ([self.delegate respondsToSelector:@selector(passcodeViewControllerDidFailAttempt:)]) {
[self.delegate passcodeViewControllerDidFailAttempt:self];
}
NSUInteger failCount = 0;
if ([self.delegate respondsToSelector:@selector(passcodeViewControllerNumberOfFailedAttempts:)]) {
failCount = [self.delegate passcodeViewControllerNumberOfFailedAttempts:self];
}
[self showFailedAttemptsCount:failCount inputView:aInputView];
// reset entered passcode
aInputView.passcode = nil;
// shake
self.viewShaker = [[AFViewShaker alloc] initWithView:aInputView.passcodeField];
[self.viewShaker shakeWithDuration:0.5f completion:nil];
// lock if needed
if ([self.delegate respondsToSelector:@selector(passcodeViewControllerLockUntilDate:)]) {
NSDate *lockUntilDate = [self.delegate passcodeViewControllerLockUntilDate:self];
if (lockUntilDate != nil) {
[self showLockMessageWithLockUntilDate:lockUntilDate];
}
}
}
}];
break;
}
case BKPasscodeViewControllerStateInputPassword:
{
if (self.type == BKPasscodeViewControllerChangePasscodeType && [self.oldPasscode isEqualToString:passcode]) {
aInputView.passcode = nil;
if (self.inputViewTitlePassword) aInputView.message = NSLocalizedStringFromTable(@"Enter a different password. Cannot re-use the same password.", @"BKPasscodeView", @"Enter a different password. Cannot re-use the same password.");
else aInputView.message = NSLocalizedStringFromTable(@"Enter a different passcode. Cannot re-use the same passcode.", @"BKPasscodeView", @"다른 암호를 입력하십시오. 동일한 암호를 다시 사용할 수 없습니다.");
} else {
self.theNewPasscode = passcode;
self.currentState = BKPasscodeViewControllerStateReinputPassword;
BKPasscodeInputView *newPasscodeInputView = [self.passcodeInputView copy];
[self customizePasscodeInputView:newPasscodeInputView];
[self updatePasscodeInputViewTitle:newPasscodeInputView];
[self.shiftingView showView:newPasscodeInputView withDirection:BKShiftingDirectionForward];
[self.passcodeInputView becomeFirstResponder];
}
break;
}
case BKPasscodeViewControllerStateReinputPassword:
{
if ([passcode isEqualToString:self.theNewPasscode]) {
if (self.touchIDManager && [BKTouchIDManager canUseTouchID]) {
[self showTouchIDSwitchView];
} else {
[self.delegate passcodeViewController:self didFinishWithPasscode:passcode];
}
} else {
self.currentState = BKPasscodeViewControllerStateInputPassword;
BKPasscodeInputView *newPasscodeInputView = [self.passcodeInputView copy];
[self customizePasscodeInputView:newPasscodeInputView];
[self updatePasscodeInputViewTitle:newPasscodeInputView];
if (self.inputViewTitlePassword) newPasscodeInputView.message = NSLocalizedStringFromTable(@"Password did not match.\nTry again.", @"BKPasscodeView", @"Password did not match.\nTry again.");
else newPasscodeInputView.message = NSLocalizedStringFromTable(@"Passcodes did not match.\nTry again.", @"BKPasscodeView", @"암호가 일치하지 않습니다.\n다시 시도하십시오.");
[self.shiftingView showView:newPasscodeInputView withDirection:BKShiftingDirectionBackward];
[self.passcodeInputView becomeFirstResponder];
}
break;
}
default:
break;
}
}
#pragma mark - BKTouchIDSwitchViewDelegate
- (void)touchIDSwitchViewDidPressDoneButton:(BKTouchIDSwitchView *)view
{
BOOL enabled = view.touchIDSwitch.isOn;
if (enabled) {
[self.touchIDManager savePasscode:self.theNewPasscode completionBlock:^(BOOL success) {
if (success) {
[self.delegate passcodeViewController:self didFinishWithPasscode:self.theNewPasscode];
} else {
if ([self.delegate respondsToSelector:@selector(passcodeViewControllerDidFailTouchIDKeychainOperation:)]) {
[self.delegate passcodeViewControllerDidFailTouchIDKeychainOperation:self];
}
}
}];
} else {
[self.touchIDManager deletePasscodeWithCompletionBlock:^(BOOL success) {
if (success) {
[self.delegate passcodeViewController:self didFinishWithPasscode:self.theNewPasscode];
} else {
if ([self.delegate respondsToSelector:@selector(passcodeViewControllerDidFailTouchIDKeychainOperation:)]) {
[self.delegate passcodeViewControllerDidFailTouchIDKeychainOperation:self];
}
}
}];
}
}
#pragma mark - Notifications
- (void)didReceiveKeyboardWillShowHideNotification:(NSNotification *)notification
{
CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
/*
#ifdef SHARE_IN
self.keyboardHeight = CGRectGetHeight(keyboardRect);
#else
UIInterfaceOrientation statusBarOrientation = [[UIApplication sharedApplication] statusBarOrientation];
self.keyboardHeight = UIInterfaceOrientationIsPortrait(statusBarOrientation) ? CGRectGetWidth(keyboardRect) : CGRectGetHeight(keyboardRect);
#endif
*/
self.keyboardHeight = CGRectGetHeight(keyboardRect);
[self.view setNeedsLayout];
}
- (void)didReceiveApplicationWillEnterForegroundNotification:(NSNotification *)notification
{
[self startTouchIDAuthenticationIfPossible];
}
@end

Просмотреть файл

@ -0,0 +1,22 @@
//
// BKShiftingView.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 10. 11..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, BKShiftingDirection) {
BKShiftingDirectionForward,
BKShiftingDirectionBackward,
};
@interface BKShiftingView : UIView
@property (nonatomic, strong) UIView *currentView;
- (void)showView:(UIView *)view withDirection:(BKShiftingDirection)direction;
@end

Просмотреть файл

@ -0,0 +1,80 @@
//
// BKShiftingView.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 10. 11..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKShiftingView.h"
@implementation BKShiftingView
- (void)layoutSubviews
{
[super layoutSubviews];
self.currentView.frame = self.bounds;
}
- (void)setCurrentView:(UIView *)currentView
{
if (_currentView == currentView) {
return;
}
[_currentView removeFromSuperview];
_currentView = currentView;
if (currentView) {
[self addSubview:currentView];
}
[self setNeedsLayout];
}
- (void)showView:(UIView *)view withDirection:(BKShiftingDirection)direction
{
UIView *oldView = self.currentView;
oldView.userInteractionEnabled = NO;
CGRect nextFrame = self.bounds;
switch (direction) {
case BKShiftingDirectionForward:
nextFrame.origin.x = CGRectGetWidth(self.bounds);
break;
case BKShiftingDirectionBackward:
nextFrame.origin.x = -CGRectGetWidth(self.bounds);
break;
}
view.frame = nextFrame;
[self addSubview:view];
// start animation
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
switch (direction) {
case BKShiftingDirectionForward:
oldView.frame = CGRectOffset(oldView.frame, -CGRectGetWidth(self.bounds), 0);
view.frame = CGRectOffset(view.frame, -CGRectGetWidth(self.bounds), 0);
break;
case BKShiftingDirectionBackward:
oldView.frame = CGRectOffset(oldView.frame, CGRectGetWidth(self.bounds), 0);
view.frame = CGRectOffset(view.frame, CGRectGetWidth(self.bounds), 0);
break;
}
} completion:^(BOOL finished) {
[oldView removeFromSuperview];
}];
_currentView = view;
}
@end

Просмотреть файл

@ -0,0 +1,27 @@
//
// BKTouchIDManager.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 10. 12..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface BKTouchIDManager : NSObject
@property (nonatomic, strong, readonly) NSString *keychainServiceName;
@property (nonatomic, strong) NSString *promptText;
@property (nonatomic, readonly, getter=isTouchIDEnabled) BOOL touchIDEnabled;
+ (BOOL)canUseTouchID;
- (instancetype)initWithKeychainServiceName:(NSString *)serviceName;
- (void)savePasscode:(NSString *)passcode completionBlock:(void(^)(BOOL success))completionBlock;
- (void)loadPasscodeWithCompletionBlock:(void(^)(NSString *passcode))completionBlock;
- (void)deletePasscodeWithCompletionBlock:(void(^)(BOOL success))completionBlock;
@end

Просмотреть файл

@ -0,0 +1,236 @@
//
// BKTouchIDManager.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 10. 12..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKTouchIDManager.h"
#import <LocalAuthentication/LocalAuthentication.h>
static NSString *const BKTouchIDManagerPasscodeAccountName = @"passcode";
static NSString *const BKTouchIDManagerTouchIDEnabledAccountName = @"enabled";
@interface BKTouchIDManager () {
dispatch_queue_t _queue;
}
@property (nonatomic, strong) NSString *keychainServiceName;
@end
@implementation BKTouchIDManager
- (instancetype)initWithKeychainServiceName:(NSString *)serviceName
{
self = [super init];
if (self) {
_queue = dispatch_queue_create("BKTouchIDManagerQueue", DISPATCH_QUEUE_SERIAL);
NSParameterAssert(serviceName);
self.keychainServiceName = serviceName;
}
return self;
}
+ (BOOL)canUseTouchID
{
if (![LAContext class]) {
return NO;
}
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
BOOL result = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
return result;
}
- (void)savePasscode:(NSString *)passcode completionBlock:(void(^)(BOOL success))completionBlock
{
NSParameterAssert(passcode);
if (NO == [[self class] canUseTouchID]) {
if (completionBlock) {
completionBlock(NO);
}
return;
}
NSString *serviceName = self.keychainServiceName;
NSData *passcodeData = [passcode dataUsingEncoding:NSUTF8StringEncoding];
dispatch_async(_queue, ^{
BOOL success = [[self class] saveKeychainItemWithServiceName:serviceName
accountName:BKTouchIDManagerPasscodeAccountName
data:passcodeData
sacFlags:kSecAccessControlUserPresence];
if (success) {
BOOL enabled = YES;
success = [[self class] saveKeychainItemWithServiceName:serviceName
accountName:BKTouchIDManagerTouchIDEnabledAccountName
data:[NSData dataWithBytes:&enabled length:sizeof(BOOL)]
sacFlags:0];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(success);
});
}
});
}
- (void)loadPasscodeWithCompletionBlock:(void (^)(NSString *))completionBlock
{
if (NO == [[self class] canUseTouchID]) {
if (completionBlock) {
completionBlock(nil);
}
return;
}
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: self.keychainServiceName,
(__bridge id)kSecAttrAccount: BKTouchIDManagerPasscodeAccountName,
(__bridge id)kSecReturnData: @YES }];
if (self.promptText) {
query[(__bridge id)kSecUseOperationPrompt] = self.promptText;
}
dispatch_async(_queue, ^{
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
NSString *result = nil;
if (status == errSecSuccess) {
NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef;
result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(result);
});
}
});
}
- (void)deletePasscodeWithCompletionBlock:(void (^)(BOOL))completionBlock
{
dispatch_async(_queue, ^{
BOOL success = ([[self class] deleteKeychainItemWithServiceName:self.keychainServiceName accountName:BKTouchIDManagerPasscodeAccountName] &&
[[self class] deleteKeychainItemWithServiceName:self.keychainServiceName accountName:BKTouchIDManagerTouchIDEnabledAccountName]);
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(success);
});
}
});
}
- (BOOL)isTouchIDEnabled
{
NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: self.keychainServiceName,
(__bridge id)kSecAttrAccount: BKTouchIDManagerTouchIDEnabledAccountName,
(__bridge id)kSecReturnData: @YES };
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
if (status == errSecSuccess) {
NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef;
BOOL result;
[resultData getBytes:&result length:sizeof(BOOL)];
return result;
} else {
return NO;
}
}
#pragma mark - Static Methods
+ (BOOL)saveKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data sacFlags:(SecAccessControlCreateFlags)sacFlags
{
// try to update first
BOOL success = [self updateKeychainItemWithServiceName:serviceName accountName:accountName data:data];
if (success) {
return YES;
}
// try deleting when update failed (workaround for iOS 8 bug)
[self deleteKeychainItemWithServiceName:serviceName accountName:accountName];
// try add
return [self addKeychainItemWithServiceName:serviceName accountName:accountName data:data sacFlags:sacFlags];
}
+ (BOOL)addKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data sacFlags:(SecAccessControlCreateFlags)sacFlags
{
CFErrorRef error = NULL;
SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
sacFlags, &error);
if (sacObject == NULL || error != NULL) {
return NO;
}
NSDictionary *attributes = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: serviceName,
(__bridge id)kSecAttrAccount: accountName,
(__bridge id)kSecValueData: data,
(__bridge id)kSecUseNoAuthenticationUI: @YES,
(__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject };
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
return (status == errSecSuccess);
}
+ (BOOL)updateKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data
{
NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: serviceName,
(__bridge id)kSecAttrAccount: accountName };
NSDictionary *changes = @{ (__bridge id)kSecValueData: data };
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes);
return (status == errSecSuccess);
}
+ (BOOL)deleteKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName
{
NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: serviceName,
(__bridge id)kSecAttrAccount: accountName };
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)(query));
return (status == errSecSuccess || status == errSecItemNotFound);
}
@end

Просмотреть файл

@ -0,0 +1,31 @@
//
// BKTouchIDSwitchView.h
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 10. 11..
// Copyright (c) 2014년 Byungkook Jang. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol BKTouchIDSwitchViewDelegate;
@interface BKTouchIDSwitchView : UIView
@property (nonatomic, weak) id<BKTouchIDSwitchViewDelegate> delegate;
@property (nonatomic, strong) UIView *switchBackgroundView;
@property (nonatomic, strong) UILabel *messageLabel;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UISwitch *touchIDSwitch;
@property (nonatomic, strong) UIButton *doneButton;
@end
@protocol BKTouchIDSwitchViewDelegate <NSObject>
- (void)touchIDSwitchViewDidPressDoneButton:(BKTouchIDSwitchView *)view;
@end

Просмотреть файл

@ -0,0 +1,125 @@
//
// BKTouchIDSwitchView.m
// BKPasscodeViewDemo
//
// Created by Byungkook Jang on 2014. 10. 11..
// Copyright (c) 2014 Byungkook Jang. All rights reserved.
//
#import "BKTouchIDSwitchView.h"
@implementation BKTouchIDSwitchView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _initialize];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self _initialize];
}
return self;
}
- (void)_initialize
{
self.switchBackgroundView = [[UIView alloc] init];
self.switchBackgroundView.backgroundColor = [UIColor whiteColor];
self.switchBackgroundView.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.switchBackgroundView.layer.borderWidth = .5f;
[self addSubview:self.switchBackgroundView];
self.messageLabel = [[UILabel alloc] init];
self.messageLabel.numberOfLines = 0;
self.messageLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.messageLabel.textAlignment = NSTextAlignmentCenter;
self.messageLabel.text = NSLocalizedStringFromTable(@"Do you want to use Touch ID for authentication?", @"BKPasscodeView", @"Touch ID를 사용하시겠습니까?");
self.messageLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
[self addSubview:self.messageLabel];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = NSLocalizedStringFromTable(@"Enable Touch ID", @"BKPasscodeView", @"Touch ID 사용");
self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
[self addSubview:self.titleLabel];
self.touchIDSwitch = [[UISwitch alloc] init];
[self addSubview:self.touchIDSwitch];
self.doneButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.doneButton.titleLabel setFont:[UIFont systemFontOfSize:20.f]];
[self.doneButton setTitle:NSLocalizedStringFromTable(@"Done", @"BKPasscodeView", @"확인") forState:UIControlStateNormal];
[self.doneButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.doneButton];
}
- (void)layoutSubviews
{
[super layoutSubviews];
UIEdgeInsets contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
static CGFloat verticalSpaces[] = { 40, 30 };
CGRect contentBounds = UIEdgeInsetsInsetRect(self.bounds, contentInset);
self.messageLabel.frame = CGRectMake(0, 0, CGRectGetWidth(contentBounds), 0);
[self.messageLabel sizeToFit];
[self.titleLabel sizeToFit];
[self.doneButton sizeToFit];
CGFloat contentHeight = (CGRectGetHeight(self.messageLabel.frame) + verticalSpaces[0] +
CGRectGetHeight(self.touchIDSwitch.frame) + verticalSpaces[1] +
CGRectGetHeight(self.doneButton.frame));
CGFloat offsetY = floorf((CGRectGetHeight(self.frame) - contentHeight) * 0.5f);
CGRect rect;
rect = self.messageLabel.frame;
rect.origin = CGPointMake(contentInset.left, offsetY);
rect.size.width = CGRectGetWidth(contentBounds);
self.messageLabel.frame = rect;
offsetY += CGRectGetHeight(rect) + verticalSpaces[0];
rect = self.touchIDSwitch.frame;
rect.origin = CGPointMake(CGRectGetMaxX(contentBounds) - CGRectGetWidth(self.touchIDSwitch.frame), offsetY);
self.touchIDSwitch.frame = rect;
rect = self.titleLabel.frame;
rect.origin = CGPointMake(contentInset.left, offsetY);
rect.size.height = CGRectGetHeight(self.touchIDSwitch.frame);
self.titleLabel.frame = rect;
offsetY += CGRectGetHeight(rect) + verticalSpaces[1];
rect = self.doneButton.frame;
rect.size.width += 10;
rect.size.height += 10;
rect.origin.x = floorf((CGRectGetWidth(self.frame) - CGRectGetWidth(rect)) * 0.5f);
rect.origin.y = offsetY;
self.doneButton.frame = rect;
self.switchBackgroundView.frame = CGRectMake(-1,
CGRectGetMinY(self.touchIDSwitch.frame) - 12,
CGRectGetWidth(self.frame) + 2,
CGRectGetHeight(self.touchIDSwitch.frame) + 24);
}
- (void)doneButtonPressed:(id)sender
{
if ([self.delegate respondsToSelector:@selector(touchIDSwitchViewDidPressDoneButton:)]) {
[self.delegate touchIDSwitchViewDidPressDoneButton:self];
}
}
@end

Просмотреть файл

@ -0,0 +1,50 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
/**
* The check mark to show selected asset.
*/
@interface CTAssetCheckmark : UIView
#pragma mark Customizing Appearance
/**
* @name Customizing Appearance
*/
/**
* To set margin of the check mark from specific edges.
*
* @param margin The margin from the edges.
* @param edgeX The layout attribute respresents vertical edge that the check mark pins to. Either `NSLayoutAttributeLeft` or `NSLayoutAttributeRight`.
* @param edgeY The layout attribute respresents horizontal edge that the check mark pins to. Either `NSLayoutAttributeTop` or `NSLayoutAttributeBottom`.
*/
- (void)setMargin:(CGFloat)margin forVerticalEdge:(NSLayoutAttribute)edgeX horizontalEdge:(NSLayoutAttribute)edgeY UI_APPEARANCE_SELECTOR;
@end

Просмотреть файл

@ -0,0 +1,165 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetCheckmark.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
/**
* The check mark to show selected asset.
*/
@interface CTAssetCheckmark ()
#pragma mark Managing Subviews
/**
* The image view of the check mark shadow.
*/
@property (nonatomic, strong) UIImageView *shadowImageView;
/**
* The image view of the check mark.
*/
@property (nonatomic, strong) UIImageView *checkmarkImageView;
#pragma mark Managing Auto Layout
/**
* The constraint for pinning the check mark to vertical edge.
*/
@property (nonatomic, strong) NSLayoutConstraint *verticalConstraint;
/**
* The constraint for pinning the check mark to horizontal edge.
*/
@property (nonatomic, strong) NSLayoutConstraint *horizontalConstraint;
/**
* Determines whether or not the constraints have been set up.
*/
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetCheckmark
#pragma mark Initializing a Check Mark Object
/**
* Designated Initializer
*
* @return an initialized check mark object
*/
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.userInteractionEnabled = NO;
self.isAccessibilityElement = NO;
[self setupViews];
}
return self;
}
#pragma mark Setting up Subviews
/**
* To setup subviews.
*/
- (void)setupViews
{
UIImage *shadowImage = [UIImage ctassetsPickerImageNamed:@"CheckmarkShadow"];
UIImageView *shadowImageView = [[UIImageView alloc] initWithImage:shadowImage];
shadowImageView.userInteractionEnabled = NO;
self.shadowImageView = shadowImageView;
[self addSubview:self.shadowImageView];
UIImage *checkmarkImage = [UIImage ctassetsPickerImageNamed:@"Checkmark"];
checkmarkImage = [checkmarkImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImageView *checkmarkImageView = [[UIImageView alloc] initWithImage:checkmarkImage];
checkmarkImageView.userInteractionEnabled = NO;
self.checkmarkImageView = checkmarkImageView;
[self addSubview:self.checkmarkImageView];
}
#pragma mark Customizing Appearance
/**
* To set margin of the check mark from specific edges.
*
* @param margin The margin from the edges.
* @param edgeX The layout attribute respresents vertical edge that the check mark pins to. Either `NSLayoutAttributeLeft` or `NSLayoutAttributeRight`.
* @param edgeY The layout attribute respresents horizontal edge that the check mark pins to. Either `NSLayoutAttributeTop` or `NSLayoutAttributeBottom`.
*/
- (void)setMargin:(CGFloat)margin forVerticalEdge:(NSLayoutAttribute)edgeX horizontalEdge:(NSLayoutAttribute)edgeY
{
NSAssert(edgeX == NSLayoutAttributeLeft || edgeX == NSLayoutAttributeRight, @"Vertical edge must be NSLayoutAttributeLeft or NSLayoutAttributeRight");
NSAssert(edgeY == NSLayoutAttributeTop || edgeY == NSLayoutAttributeBottom, @"Horizontal edge must be NSLayoutAttributeTop or NSLayoutAttributeBottom");
[self.superview removeConstraints:@[self.verticalConstraint, self.horizontalConstraint]];
self.verticalConstraint = [self autoPinEdgeToSuperviewEdge:(ALEdge)edgeX withInset:margin];
self.horizontalConstraint = [self autoPinEdgeToSuperviewEdge:(ALEdge)edgeY withInset:margin];
}
#pragma mark Triggering Auto Layout
/**
* Updates constraints of the check mark.
*/
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
CGSize size = [UIImage ctassetsPickerImageNamed:@"CheckmarkShadow"].size;
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
[self autoSetDimensionsToSize:size];
}];
[self.shadowImageView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.checkmarkImageView autoCenterInSuperview];
self.verticalConstraint = [self autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0];
self.horizontalConstraint = [self autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:0];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
@end

Просмотреть файл

@ -0,0 +1,54 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
#import "CTAssetThumbnailStacks.h"
@interface CTAssetCollectionViewCell : UITableViewCell
@property (nonatomic, strong, readonly, nonnull) CTAssetThumbnailStacks *thumbnailStacks;
@property (nonatomic, weak, nullable) UIFont *titleFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *titleTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *selectedTitleTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, weak, nullable) UIFont *countFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *countTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *selectedCountTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *accessoryColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *selectedAccessoryColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, weak, nullable) UIColor *selectedBackgroundColor UI_APPEARANCE_SELECTOR;
- (instancetype)initWithThumbnailSize:(CGSize)size reuseIdentifier:(nullable NSString *)reuseIdentifier;
- (void)bind:(nonnull PHAssetCollection *)collection count:(NSUInteger)count;
@end

Просмотреть файл

@ -0,0 +1,319 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetCollectionViewCell.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
#import "NSNumberFormatter+CTAssetsPickerController.h"
@interface CTAssetCollectionViewCell ()
@property (nonatomic, assign) CGSize thumbnailSize;
@property (nonatomic, strong) CTAssetThumbnailStacks *thumbnailStacks;
@property (nonatomic, strong) UIView *labelsView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *countLabel;
@property (nonatomic, assign) BOOL didSetupConstraints;
@property (nonatomic, strong) PHAssetCollection *collection;
@property (nonatomic, assign) NSUInteger count;
@end
@implementation CTAssetCollectionViewCell
- (instancetype)initWithThumbnailSize:(CGSize)size reuseIdentifier:(NSString *)reuseIdentifier;
{
if (self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier])
{
_thumbnailSize = size;
_titleTextColor = CTAssetCollectionViewCellTitleTextColor;
_selectedTitleTextColor = CTAssetCollectionViewCellTitleTextColor;
_countTextColor = CTAssetCollectionViewCellCountTextColor;
_selectedCountTextColor = CTAssetCollectionViewCellCountTextColor;
_accessoryColor = CTAssetCollectionViewCellAccessoryColor;
_selectedAccessoryColor = CTAssetCollectionViewCellAccessoryColor;
self.opaque = YES;
self.isAccessibilityElement = YES;
self.textLabel.backgroundColor = self.backgroundColor;
self.detailTextLabel.backgroundColor = self.backgroundColor;
self.accessoryType = UITableViewCellAccessoryNone;
[self setupViews];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
CTAssetThumbnailStacks *thumbnailStacks = [CTAssetThumbnailStacks newAutoLayoutView];
thumbnailStacks.thumbnailSize = self.thumbnailSize;
self.thumbnailStacks = thumbnailStacks;
UILabel *titleLabel = [UILabel newAutoLayoutView];
titleLabel.font = CTAssetCollectionViewCellTitleFont;
titleLabel.textColor = self.titleTextColor;
self.titleLabel = titleLabel;
UILabel *countLabel = [UILabel newAutoLayoutView];
countLabel.font = CTAssetCollectionViewCellCountFont;
countLabel.textColor = self.countTextColor;
self.countLabel = countLabel;
UIView *labelsView = [UIView newAutoLayoutView];
[labelsView addSubview:self.titleLabel];
[labelsView addSubview:self.countLabel];
self.labelsView = labelsView;
[self.contentView addSubview:self.thumbnailStacks];
[self.contentView addSubview:self.labelsView];
UIImage *accessory = [UIImage ctassetsPickerImageNamed:@"DisclosureArrow"];
accessory = [accessory imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImageView *accessoryView = [[UIImageView alloc] initWithImage:accessory];
accessoryView.tintColor = self.accessoryColor;
self.accessoryView = accessoryView;
}
- (void)setupPlaceholderImage
{
NSString *imageName = [self placeHolderImageNameOfCollectionSubtype:self.collection.assetCollectionSubtype];
UIImage *image = [UIImage ctassetsPickerImageNamed:imageName];
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
for (CTAssetThumbnailView *thumbnailView in self.thumbnailStacks.thumbnailViews)
{
[thumbnailView bind:nil assetCollection:nil];
thumbnailView.backgroundImage = image;
}
}
- (NSString *)placeHolderImageNameOfCollectionSubtype:(PHAssetCollectionSubtype)subtype
{
if (subtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary)
return @"GridEmptyCameraRoll";
else if (subtype == PHAssetCollectionSubtypeSmartAlbumAllHidden)
return @"GridHiddenAlbum";
else if (subtype == PHAssetCollectionSubtypeAlbumCloudShared)
return @"GridEmptyAlbumShared";
else
return @"GridEmptyAlbum";
}
#pragma mark - Apperance
- (UIFont *)titleFont
{
return self.titleLabel.font;
}
- (void)setTitleFont:(UIFont *)titleFont
{
UIFont *font = (titleFont) ? titleFont : CTAssetCollectionViewCellTitleFont;
self.titleLabel.font = font;
}
- (void)setTitleTextColor:(UIColor *)titleTextColor
{
UIColor *color = (titleTextColor) ? titleTextColor : CTAssetCollectionViewCellTitleTextColor;
_titleTextColor = color;
}
- (void)setSelectedTitleTextColor:(UIColor *)titleTextColor
{
UIColor *color = (titleTextColor) ? titleTextColor : CTAssetCollectionViewCellTitleTextColor;
_selectedTitleTextColor = color;
}
- (UIFont *)countFont
{
return self.countLabel.font;
}
- (void)setCountFont:(UIFont *)countFont
{
UIFont *font = (countFont) ? countFont : CTAssetCollectionViewCellCountFont;
self.countLabel.font = font;
}
- (void)setCountTextColor:(UIColor *)countTextColor
{
UIColor *color = (countTextColor) ? countTextColor : CTAssetCollectionViewCellCountTextColor;
_countTextColor = color;
}
- (void)setSelectedCountTextColor:(UIColor *)countTextColor
{
UIColor *color = (countTextColor) ? countTextColor : CTAssetCollectionViewCellCountTextColor;
_selectedCountTextColor = color;
}
- (void)setAccessoryColor:(UIColor *)accessoryColor
{
UIColor *color = (accessoryColor) ? accessoryColor : CTAssetCollectionViewCellAccessoryColor;
_accessoryColor = color;
}
- (void)setSelectedAccessoryColor:(UIColor *)accessoryColor
{
UIColor *color = (accessoryColor) ? accessoryColor : CTAssetCollectionViewCellAccessoryColor;
_selectedAccessoryColor = color;
}
- (UIColor *)selectedBackgroundColor
{
return self.selectedBackgroundView.backgroundColor;
}
- (void)setSelectedBackgroundColor:(UIColor *)selectedBackgroundColor
{
if (!selectedBackgroundColor)
self.selectedBackgroundView = nil;
else
{
UIView *view = [UIView new];
view.backgroundColor = selectedBackgroundColor;
self.selectedBackgroundView = view;
}
}
#pragma mark - Override highlighted / selected
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
[super setHighlighted:highlighted animated:animated];
[self.thumbnailStacks setHighlighted:highlighted];
self.titleLabel.textColor = (highlighted) ? self.selectedTitleTextColor : self.titleTextColor;
self.countLabel.textColor = (highlighted) ? self.selectedCountTextColor : self.countTextColor;
self.accessoryView.tintColor = (highlighted) ? self.selectedAccessoryColor : self.accessoryColor;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
[self.thumbnailStacks setHighlighted:selected];
self.titleLabel.textColor = (selected) ? self.selectedTitleTextColor : self.titleTextColor;
self.countLabel.textColor = (selected) ? self.selectedCountTextColor : self.countTextColor;
self.accessoryView.tintColor = (selected) ? self.selectedAccessoryColor : self.accessoryColor;
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
CGSize size = self.thumbnailSize;
CGFloat top = self.thumbnailStacks.edgeInsets.top;
size.height += top;
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
[self.thumbnailStacks autoSetDimensionsToSize:size];
}];
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
[self.thumbnailStacks autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeTrailing];
}];
[self.labelsView autoAlignAxisToSuperviewAxis:ALAxisHorizontal];
[self.labelsView autoPinEdge:ALEdgeLeading
toEdge:ALEdgeTrailing
ofView:self.thumbnailStacks
withOffset:self.labelsView.layoutMargins.left
relation:NSLayoutRelationGreaterThanOrEqual];
[self.titleLabel autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeBottom];
[self.countLabel autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeTop];
[self.countLabel autoPinEdge:ALEdgeTop
toEdge:ALEdgeBottom
ofView:self.titleLabel
withOffset:self.countLabel.layoutMargins.top
relation:NSLayoutRelationGreaterThanOrEqual];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
#pragma mark - Bind asset collection
- (void)bind:(PHAssetCollection *)collection count:(NSUInteger)count
{
self.collection = collection;
self.count = count;
[self setupPlaceholderImage];
self.titleLabel.text = collection.localizedTitle;
if (count != NSNotFound)
{
NSNumberFormatter *nf = [NSNumberFormatter new];
self.countLabel.text = [nf ctassetsPickerStringFromAssetsCount:count];
}
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
#pragma mark - Accessibility label
- (NSString *)accessibilityLabel
{
NSString *title = self.titleLabel.text;
NSString *count = [NSString stringWithFormat:CTAssetsPickerLocalizedString(@"%@ Photos", nil), self.countLabel.text];
NSArray *labels = @[title, count];
return [labels componentsJoinedByString:@","];
}
@end

Просмотреть файл

@ -0,0 +1,33 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetCollectionViewController : UITableViewController
- (void)reloadUserInterface;
@end

Просмотреть файл

@ -0,0 +1,564 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import "CTAssetsPickerDefines.h"
#import "CTAssetsPickerController.h"
#import "CTAssetsPickerController+Internal.h"
#import "CTAssetCollectionViewController.h"
#import "CTAssetCollectionViewCell.h"
#import "CTAssetsGridViewController.h"
#import "PHAssetCollection+CTAssetsPickerController.h"
#import "PHAsset+CTAssetsPickerController.h"
#import "PHImageManager+CTAssetsPickerController.h"
#import "NSBundle+CTAssetsPickerController.h"
@interface CTAssetCollectionViewController()
<PHPhotoLibraryChangeObserver, CTAssetsGridViewControllerDelegate>
@property (nonatomic, weak) CTAssetsPickerController *picker;
@property (nonatomic, strong) UIBarButtonItem *cancelButton;
@property (nonatomic, strong) UIBarButtonItem *doneButton;
@property (nonatomic, copy) NSArray *fetchResults;
@property (nonatomic, copy) NSArray *assetCollections;
@property (nonatomic, strong) PHCachingImageManager *imageManager;
@property (nonatomic, strong) PHAssetCollection *defaultAssetCollection;
@property (nonatomic, assign) BOOL didShowDefaultAssetCollection;
@property (nonatomic, assign) BOOL didSelectDefaultAssetCollection;
@end
@implementation CTAssetCollectionViewController
- (instancetype)init
{
if (self = [super initWithStyle:UITableViewStylePlain])
{
_imageManager = [PHCachingImageManager new];
[self addNotificationObserver];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
[self localize];
[self setupDefaultAssetCollection];
[self setupFetchResults];
[self registerChangeObserver];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupButtons];
[self updateTitle:self.picker.selectedAssets];
[self updateButton:self.picker.selectedAssets];
[self selectDefaultAssetCollection];
}
- (void)dealloc
{
[self unregisterChangeObserver];
[self removeNotificationObserver];
}
#pragma mark - Reload user interface
- (void)reloadUserInterface
{
[self setupViews];
[self setupButtons];
[self localize];
[self setupDefaultAssetCollection];
[self setupFetchResults];
}
#pragma mark - Accessors
- (CTAssetsPickerController *)picker
{
return (CTAssetsPickerController *)self.splitViewController.parentViewController;
}
- (NSIndexPath *)indexPathForAssetCollection:(PHAssetCollection *)assetCollection
{
NSInteger row = [self.assetCollections indexOfObject:assetCollection];
if (row != NSNotFound)
return [NSIndexPath indexPathForRow:row inSection:0];
else
return nil;
}
#pragma mark - Setup
- (void)setupViews
{
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight =
self.picker.assetCollectionThumbnailSize.height +
self.tableView.layoutMargins.top +
self.tableView.layoutMargins.bottom;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
- (void)setupButtons
{
if (self.doneButton == nil)
{
NSString *title = (self.picker.doneButtonTitle) ?
self.picker.doneButtonTitle : CTAssetsPickerLocalizedString(@"Done", nil);
self.doneButton =
[[UIBarButtonItem alloc] initWithTitle:title
style:UIBarButtonItemStyleDone
target:self.picker
action:@selector(finishPickingAssets:)];
}
if (self.cancelButton == nil)
{
self.cancelButton =
[[UIBarButtonItem alloc] initWithTitle:CTAssetsPickerLocalizedString(@"Cancel", nil)
style:UIBarButtonItemStylePlain
target:self.picker
action:@selector(dismiss:)];
}
}
- (void)localize
{
[self resetTitle];
}
- (void)setupFetchResults
{
NSMutableArray *fetchResults = [NSMutableArray new];
for (NSNumber *subtypeNumber in self.picker.assetCollectionSubtypes)
{
PHAssetCollectionType type = [PHAssetCollection ctassetPickerAssetCollectionTypeOfSubtype:subtypeNumber.integerValue];
PHAssetCollectionSubtype subtype = subtypeNumber.integerValue;
PHFetchResult *fetchResult =
[PHAssetCollection fetchAssetCollectionsWithType:type
subtype:subtype
options:self.picker.assetCollectionFetchOptions];
[fetchResults addObject:fetchResult];
}
self.fetchResults = [NSMutableArray arrayWithArray:fetchResults];
[self updateAssetCollections];
[self reloadData];
[self showDefaultAssetCollection];
}
- (void)updateAssetCollections
{
NSMutableArray *assetCollections = [NSMutableArray new];
for (PHFetchResult *fetchResult in self.fetchResults)
{
for (PHAssetCollection *assetCollection in fetchResult)
{
BOOL showsAssetCollection = YES;
if (!self.picker.showsEmptyAlbums)
{
PHFetchOptions *options = [PHFetchOptions new];
options.predicate = self.picker.assetsFetchOptions.predicate;
if ([options respondsToSelector:@selector(setFetchLimit:)])
options.fetchLimit = 1;
NSInteger count = [assetCollection ctassetPikcerCountOfAssetsFetchedWithOptions:options];
showsAssetCollection = (count > 0);
}
if (showsAssetCollection)
[assetCollections addObject:assetCollection];
}
}
self.assetCollections = [NSMutableArray arrayWithArray:assetCollections];
}
- (void)setupDefaultAssetCollection
{
if (!self.picker || self.picker.defaultAssetCollection == PHAssetCollectionSubtypeAny) {
self.defaultAssetCollection = nil;
return;
}
PHAssetCollectionType type = [PHAssetCollection ctassetPickerAssetCollectionTypeOfSubtype:self.picker.defaultAssetCollection];
PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:type subtype:self.picker.defaultAssetCollection options:self.picker.assetCollectionFetchOptions];
self.defaultAssetCollection = fetchResult.firstObject;
}
#pragma mark - Rotation
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self updateTitle:self.picker.selectedAssets];
[self updateButton:self.picker.selectedAssets];
} completion:nil];
}
#pragma mark - Notifications
- (void)addNotificationObserver
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(selectedAssetsChanged:)
name:CTAssetsPickerSelectedAssetsDidChangeNotification
object:nil];
[center addObserver:self
selector:@selector(contentSizeCategoryChanged:)
name:UIContentSizeCategoryDidChangeNotification
object:nil];
}
- (void)removeNotificationObserver
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:CTAssetsPickerSelectedAssetsDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil];
}
#pragma mark - Photo library change observer
- (void)registerChangeObserver
{
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
}
- (void)unregisterChangeObserver
{
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
}
#pragma mark - Photo library changed
- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *updatedFetchResults = nil;
for (PHFetchResult *fetchResult in self.fetchResults)
{
PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:fetchResult];
if (changeDetails)
{
if (!updatedFetchResults)
updatedFetchResults = [self.fetchResults mutableCopy];
updatedFetchResults[[self.fetchResults indexOfObject:fetchResult]] = changeDetails.fetchResultAfterChanges;
}
}
if (updatedFetchResults)
{
self.fetchResults = updatedFetchResults;
[self updateAssetCollections];
[self reloadData];
}
});
}
#pragma mark - Selected assets changed
- (void)selectedAssetsChanged:(NSNotification *)notification
{
NSArray *selectedAssets = (NSArray *)notification.object;
[self updateTitle:selectedAssets];
[self updateButton:selectedAssets];
}
- (void)updateTitle:(NSArray *)selectedAssets
{
if ([self isTopViewController] && selectedAssets.count > 0)
self.title = self.picker.selectedAssetsString;
else
[self resetTitle];
}
- (void)updateButton:(NSArray *)selectedAssets
{
self.navigationItem.leftBarButtonItem = (self.picker.showsCancelButton) ? self.cancelButton : nil;
self.navigationItem.rightBarButtonItem = [self isTopViewController] ? self.doneButton : nil;
if (self.picker.alwaysEnableDoneButton)
self.navigationItem.rightBarButtonItem.enabled = YES;
else
self.navigationItem.rightBarButtonItem.enabled = (self.picker.selectedAssets.count > 0);
}
- (BOOL)isTopViewController
{
UIViewController *vc = self.splitViewController.viewControllers.lastObject;
if ([vc isMemberOfClass:[UINavigationController class]])
return (self == ((UINavigationController *)vc).topViewController);
else
return NO;
}
- (void)resetTitle
{
if (!self.picker.title)
self.title = CTAssetsPickerLocalizedString(@"Photos", nil);
else
self.title = self.picker.title;
}
#pragma mark - Content size category changed
- (void)contentSizeCategoryChanged:(NSNotification *)notification
{
[self reloadData];
}
#pragma mark - Reload data
- (void)reloadData
{
if (self.assetCollections.count > 0)
[self.tableView reloadData];
else
[self.picker showNoAssets];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.assetCollections.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
PHAssetCollection *collection = self.assetCollections[indexPath.row];
NSUInteger count;
if (self.picker.showsNumberOfAssets)
count = [collection ctassetPikcerCountOfAssetsFetchedWithOptions:self.picker.assetsFetchOptions];
else
count = NSNotFound;
static NSString *cellIdentifier = @"CellIdentifier";
CTAssetCollectionViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
cell = [[CTAssetCollectionViewCell alloc] initWithThumbnailSize:self.picker.assetCollectionThumbnailSize
reuseIdentifier:cellIdentifier];
[cell bind:collection count:count];
[self requestThumbnailsForCell:cell assetCollection:collection];
return cell;
}
- (void)requestThumbnailsForCell:(CTAssetCollectionViewCell *)cell assetCollection:(PHAssetCollection *)collection
{
NSUInteger count = cell.thumbnailStacks.thumbnailViews.count;
NSArray *assets = [self posterAssetsFromAssetCollection:collection count:count];
CGSize targetSize = [self.picker imageSizeForContainerSize:self.picker.assetCollectionThumbnailSize];
for (NSUInteger index = 0; index < count; index++)
{
CTAssetThumbnailView *thumbnailView = [cell.thumbnailStacks thumbnailAtIndex:index];
thumbnailView.hidden = (assets.count > 0) ? YES : NO;
if (index < assets.count)
{
PHAsset *asset = assets[index];
[self.imageManager ctassetsPickerRequestImageForAsset:asset
targetSize:targetSize
contentMode:PHImageContentModeAspectFill
options:self.picker.thumbnailRequestOptions
resultHandler:^(UIImage *image, NSDictionary *info){
[thumbnailView setHidden:NO];
[thumbnailView bind:image assetCollection:collection];
}];
}
}
}
- (NSArray *)posterAssetsFromAssetCollection:(PHAssetCollection *)collection count:(NSUInteger)count;
{
PHFetchOptions *options = [PHFetchOptions new];
options.predicate = self.picker.assetsFetchOptions.predicate; // aligned specified predicate
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *result = [PHAsset fetchKeyAssetsInAssetCollection:collection options:options];
NSUInteger location = 0;
NSUInteger length = (result.count < count) ? result.count : count;
NSArray *assets = [self itemsFromFetchResult:result range:NSMakeRange(location, length)];
return assets;
}
- (NSArray *)itemsFromFetchResult:(PHFetchResult *)result range:(NSRange)range
{
if (result.count == 0)
return nil;
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
NSArray *array = [result objectsAtIndexes:indexSet];
return array;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
PHAssetCollection *collection = self.assetCollections[indexPath.row];
CTAssetsGridViewController *vc = [CTAssetsGridViewController new];
vc.title = self.picker.selectedAssetsString ? : collection.localizedTitle;
vc.assetCollection = collection;
vc.delegate = self;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.delegate = (id<UINavigationControllerDelegate>)self.picker;
[self.picker setShouldCollapseDetailViewController:NO];
[self.splitViewController showDetailViewController:nav sender:nil];
}
#pragma mark - Show / select default asset collection
- (void)showDefaultAssetCollection
{
if (self.defaultAssetCollection && !self.didShowDefaultAssetCollection)
{
CTAssetsGridViewController *vc = [CTAssetsGridViewController new];
vc.title = self.picker.selectedAssetsString ? : self.defaultAssetCollection.localizedTitle;
vc.assetCollection = self.defaultAssetCollection;
vc.delegate = self;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.delegate = (id<UINavigationControllerDelegate>)self.picker;
[self.picker setShouldCollapseDetailViewController:(self.picker.modalPresentationStyle == UIModalPresentationFormSheet)];
[self.splitViewController showDetailViewController:nav sender:nil];
NSIndexPath *indexPath = [self indexPathForAssetCollection:self.defaultAssetCollection];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
self.didShowDefaultAssetCollection = YES;
}
}
- (void)selectDefaultAssetCollection
{
if (self.defaultAssetCollection && !self.didSelectDefaultAssetCollection)
{
NSIndexPath *indexPath = [self indexPathForAssetCollection:self.defaultAssetCollection];
if (indexPath)
{
[UIView animateWithDuration:0.0f
animations:^{
[self.tableView selectRowAtIndexPath:indexPath
animated:(!self.splitViewController.collapsed)
scrollPosition:UITableViewScrollPositionTop];
}
completion:^(BOOL finished){
// mimic clearsSelectionOnViewWillAppear
if (finished && self.splitViewController.collapsed)
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}];
}
self.didSelectDefaultAssetCollection = YES;
}
}
#pragma mark - Grid view controller delegate
- (void)assetsGridViewController:(CTAssetsGridViewController *)picker photoLibraryDidChangeForAssetCollection:(PHAssetCollection *)assetCollection
{
NSIndexPath *indexPath = [self indexPathForAssetCollection:assetCollection];
if (indexPath)
{
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
@end

Просмотреть файл

@ -0,0 +1,45 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
NS_ASSUME_NONNULL_BEGIN
@interface CTAssetItemViewController : UIViewController
@property (nonatomic, assign) BOOL allowsSelection;
@property (nonatomic, strong, readonly) PHAsset *asset;
@property (nonatomic, strong, readonly, nullable) UIImage *image;
+ (CTAssetItemViewController *)assetItemViewControllerForAsset:(PHAsset *)asset;
- (void)playAsset:(nullable id)sender;
- (void)pauseAsset:(nullable id)sender;
@end
NS_ASSUME_NONNULL_END

Просмотреть файл

@ -0,0 +1,422 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerController.h"
#import "CTAssetItemViewController.h"
#import "CTAssetScrollView.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "PHAsset+CTAssetsPickerController.h"
#import "PHImageManager+CTAssetsPickerController.h"
@interface CTAssetItemViewController ()
@property (nonatomic, weak) CTAssetsPickerController *picker;
@property (nonatomic, strong) PHAsset *asset;
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) PHImageManager *imageManager;
@property (nonatomic, assign) PHImageRequestID imageRequestID;
@property (nonatomic, assign) PHImageRequestID playerItemRequestID;
@property (nonatomic, strong) CTAssetScrollView *scrollView;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetItemViewController
+ (CTAssetItemViewController *)assetItemViewControllerForAsset:(PHAsset *)asset
{
return [[self alloc] initWithAsset:asset];
}
- (instancetype)initWithAsset:(PHAsset *)asset
{
if (self = [super init])
{
_imageManager = [PHImageManager defaultManager];
self.asset = asset;
self.allowsSelection = NO;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupScrollViewButtons];
[self requestAssetImage];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self pauseAsset:self.view];
[self cancelRequestAsset];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self.scrollView setNeedsUpdateConstraints];
[self.scrollView updateConstraintsIfNeeded];
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self.scrollView updateZoomScalesAndZoom:YES];
} completion:nil];
}
#pragma mark - Accessors
- (CTAssetsPickerController *)picker
{
return (CTAssetsPickerController *)self.splitViewController.parentViewController;
}
#pragma mark - Setup
- (void)setupViews
{
CTAssetScrollView *scrollView = [CTAssetScrollView newAutoLayoutView];
scrollView.allowsSelection = self.allowsSelection;
self.scrollView = scrollView;
[self.view addSubview:self.scrollView];
[self.view layoutIfNeeded];
}
- (void)setupScrollViewButtons
{
CTAssetPlayButton *playButton = self.scrollView.playButton;
[playButton addTarget:self action:@selector(playAsset:) forControlEvents:UIControlEventTouchUpInside];
CTAssetSelectionButton *selectionButton = self.scrollView.selectionButton;
selectionButton.enabled = [self assetScrollView:self.scrollView shouldEnableAsset:self.asset];
selectionButton.selected = [self.picker.selectedAssets containsObject:self.asset];
[selectionButton addTarget:self action:@selector(selectionButtonTouchDown:) forControlEvents:UIControlEventTouchDown];
[selectionButton addTarget:self action:@selector(selectionButtonTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
}
#pragma mark - Cancel request
- (void)cancelRequestAsset
{
[self cancelRequestImage];
[self cancelRequestPlayerItem];
}
- (void)cancelRequestImage
{
if (self.imageRequestID)
{
[self.scrollView setProgress:1];
[self.imageManager cancelImageRequest:self.imageRequestID];
}
}
- (void)cancelRequestPlayerItem
{
if (self.playerItemRequestID)
{
[self.scrollView stopActivityAnimating];
[self.imageManager cancelImageRequest:self.playerItemRequestID];
}
}
#pragma mark - Request image
- (void)requestAssetImage
{
[self.scrollView setProgress:0];
CGSize targetSize = [self targetImageSize];
PHImageRequestOptions *options = [self imageRequestOptions];
self.imageRequestID =
[self.imageManager ctassetsPickerRequestImageForAsset:self.asset
targetSize:targetSize
contentMode:PHImageContentModeAspectFit
options:options
resultHandler:^(UIImage *image, NSDictionary *info) {
// this image is set for transition animation
self.image = image;
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = info[PHImageErrorKey];
if (error)
[self showRequestImageError:error title:nil];
else
[self.scrollView bind:self.asset image:image requestInfo:info];
});
}];
}
- (CGSize)targetImageSize
{
UIScreen *screen = UIScreen.mainScreen;
CGFloat scale = screen.scale;
return CGSizeMake(CGRectGetWidth(screen.bounds) * scale, CGRectGetHeight(screen.bounds) * scale);
}
- (PHImageRequestOptions *)imageRequestOptions
{
PHImageRequestOptions *options = [PHImageRequestOptions new];
options.networkAccessAllowed = YES;
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.scrollView setProgress:progress];
});
};
return options;
}
#pragma mark - Request player item
- (void)requestAssetPlayerItem:(id)sender
{
[self.scrollView startActivityAnimating];
PHVideoRequestOptions *options = [self videoRequestOptions];
self.playerItemRequestID =
[self.imageManager requestPlayerItemForVideo:self.asset
options:options
resultHandler:^(AVPlayerItem *playerItem, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = info[PHImageErrorKey];
NSString * title = CTAssetsPickerLocalizedString(@"Cannot Play Stream Video", nil);
if (error)
[self showRequestVideoError:error title:title];
else
[self.scrollView bind:playerItem requestInfo:info];
});
}];
}
- (PHVideoRequestOptions *)videoRequestOptions
{
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.networkAccessAllowed = YES;
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
//do nothing
});
};
return options;
}
#pragma mark - Request error
- (void)showRequestImageError:(NSError *)error title:(NSString *)title
{
[self.scrollView setProgress:1];
[self showRequestError:error title:title];
}
- (void)showRequestVideoError:(NSError *)error title:(NSString *)title
{
[self.scrollView stopActivityAnimating];
[self showRequestError:error title:title];
}
- (void)showRequestError:(NSError *)error title:(NSString *)title
{
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:title
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action =
[UIAlertAction actionWithTitle:CTAssetsPickerLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Playback
- (void)playAsset:(id)sender
{
if (!self.scrollView.player)
[self requestAssetPlayerItem:sender];
else
[self.scrollView playVideo];
}
- (void)pauseAsset:(id)sender
{
if (!self.scrollView.player)
[self cancelRequestPlayerItem];
else
[self.scrollView pauseVideo];
}
#pragma mark - Selection
- (void)selectionButtonTouchDown:(id)sender
{
PHAsset *asset = self.asset;
CTAssetScrollView *scrollView = self.scrollView;
if ([self assetScrollView:scrollView shouldHighlightAsset:asset])
[self assetScrollView:scrollView didHighlightAsset:asset];
}
- (void)selectionButtonTouchUpInside:(id)sender
{
PHAsset *asset = self.asset;
CTAssetScrollView *scrollView = self.scrollView;
CTAssetSelectionButton *selectionButton = scrollView.selectionButton;
if (!selectionButton.selected)
{
if ([self assetScrollView:scrollView shouldSelectAsset:asset])
{
[self.picker selectAsset:asset];
[selectionButton setSelected:YES];
[self assetScrollView:scrollView didSelectAsset:asset];
}
}
else
{
if ([self assetScrollView:scrollView shouldDeselectAsset:asset])
{
[self.picker deselectAsset:asset];
[selectionButton setSelected:NO];
[self assetScrollView:scrollView didDeselectAsset:asset];
}
}
[self assetScrollView:self.scrollView didUnhighlightAsset:self.asset];
}
#pragma mark - Asset scrollView delegate
- (BOOL)assetScrollView:(CTAssetScrollView *)scrollView shouldEnableAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)])
return [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
else
return YES;
}
- (BOOL)assetScrollView:(CTAssetScrollView *)scrollView shouldSelectAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)])
return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
else
return YES;
}
- (void)assetScrollView:(CTAssetScrollView *)scrollView didSelectAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)])
[self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
}
- (BOOL)assetScrollView:(CTAssetScrollView *)scrollView shouldDeselectAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)])
return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
else
return YES;
}
- (void)assetScrollView:(CTAssetScrollView *)scrollView didDeselectAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)])
[self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
}
- (BOOL)assetScrollView:(CTAssetScrollView *)scrollView shouldHighlightAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)])
return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
else
return YES;
}
- (void)assetScrollView:(CTAssetScrollView *)scrollView didHighlightAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)])
[self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
}
- (void)assetScrollView:(CTAssetScrollView *)scrollView didUnhighlightAsset:(PHAsset *)asset
{
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)])
[self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
}
@end

Просмотреть файл

@ -0,0 +1,31 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetPlayButton : UIControl
@end

Просмотреть файл

@ -0,0 +1,180 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetPlayButton.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
@interface CTAssetPlayButton ()
@property (nonatomic, strong) UIVisualEffectView *blurView;
@property (nonatomic, strong) UIVisualEffectView *vibrancyView;
@property (nonatomic, strong) UIView *vibrancyFill;
@property (nonatomic, strong) UIView *highlightedView;
@property (nonatomic, strong) UIView *colorView;
@property (nonatomic, strong) UIImageView *glyphMask;
@property (nonatomic, strong) UIImageView *buttonMask;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetPlayButton
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.isAccessibilityElement = YES;
self.accessibilityTraits = UIAccessibilityTraitButton;
[self setupViews];
[self localize];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
[self setupEffectViews];
[self setupHightlightedView];
[self setupColorView];
[self setupMaskViews];
}
- (void)setupEffectViews
{
// Blur effect
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurView.userInteractionEnabled = NO;
self.blurView = blurView;
// Vibrancy effect
UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect effectForBlurEffect:blurEffect];
UIVisualEffectView *vibrancyView = [[UIVisualEffectView alloc] initWithEffect:vibrancyEffect];
vibrancyView.userInteractionEnabled = NO;
self.vibrancyView = vibrancyView;
UIView *vibrancyFill = [UIView newAutoLayoutView];
vibrancyFill.backgroundColor = [UIColor whiteColor];
vibrancyFill.userInteractionEnabled = NO;
self.vibrancyFill = vibrancyFill;
// Add fill to the vibrancy view
[vibrancyView.contentView addSubview:self.vibrancyFill];
[blurView.contentView addSubview:self.vibrancyView];
[self addSubview:blurView];
}
- (void)setupHightlightedView
{
UIView *highlightedView = [UIView newAutoLayoutView];
highlightedView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
highlightedView.userInteractionEnabled = NO;
highlightedView.hidden = YES;
self.highlightedView = highlightedView;
[self addSubview:self.highlightedView];
}
- (void)setupColorView
{
UIView *colorView = [UIView newAutoLayoutView];
colorView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.8];
colorView.userInteractionEnabled = NO;
self.colorView = colorView;
[self addSubview:self.colorView];
}
- (void)setupMaskViews
{
UIImage *glyphMaskImage = [UIImage ctassetsPickerImageNamed:@"VideoPlayGlyphMask"];
UIImageView *glyphMask = [[UIImageView alloc] initWithImage:glyphMaskImage];
glyphMask.userInteractionEnabled = NO;
self.colorView.maskView = glyphMask;
UIImage *buttonMaskImage = [UIImage ctassetsPickerImageNamed:@"VideoPlayButtonMask"];
UIImageView *buttonMask = [[UIImageView alloc] initWithImage:buttonMaskImage];
buttonMask.userInteractionEnabled = NO;
self.maskView = buttonMask;
}
- (void)localize
{
self.accessibilityLabel = CTAssetsPickerLocalizedString(@"Play", nil);
}
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
CGSize size = [UIImage ctassetsPickerImageNamed:@"VideoPlayButtonMask"].size;
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
[self autoSetDimensionsToSize:size];
}];
[self.blurView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.vibrancyView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.vibrancyFill autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.highlightedView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.colorView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
#pragma mark - States
- (void)setHighlighted:(BOOL)highlighted
{
super.highlighted = highlighted;
self.highlightedView.hidden = !highlighted;
}
@end

Просмотреть файл

@ -0,0 +1,67 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import "CTAssetItemViewController.h"
#import "CTAssetPlayButton.h"
#import "CTAssetSelectionButton.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString * const CTAssetScrollViewDidTapNotification;
extern NSString * const CTAssetScrollViewPlayerWillPlayNotification;
extern NSString * const CTAssetScrollViewPlayerWillPauseNotification;
@interface CTAssetScrollView : UIScrollView
@property (nonatomic, assign) BOOL allowsSelection;
@property (nonatomic, strong, readonly, nullable) UIImage *image;
@property (nonatomic, strong, readonly, nullable) AVPlayer *player;
@property (nonatomic, strong, readonly) UIImageView *imageView;
@property (nonatomic, strong, readonly) CTAssetPlayButton *playButton;
@property (nonatomic, strong, readonly) CTAssetSelectionButton *selectionButton;
- (void)startActivityAnimating;
- (void)stopActivityAnimating;
- (void)setProgress:(CGFloat)progress;
- (void)bind:(PHAsset *)asset image:(nullable UIImage *)image requestInfo:(nullable NSDictionary<NSString*, id> *)info;
- (void)bind:(AVPlayerItem *)playerItem requestInfo:(nullable NSDictionary *)info;
- (void)updateZoomScalesAndZoom:(BOOL)zoom;
- (void)playVideo;
- (void)pauseVideo;
@end
NS_ASSUME_NONNULL_END

Просмотреть файл

@ -0,0 +1,713 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetScrollView.h"
#import "CTAssetPlayButton.h"
#import "PHAsset+CTAssetsPickerController.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
NSString * const CTAssetScrollViewDidTapNotification = @"CTAssetScrollViewDidTapNotification";
NSString * const CTAssetScrollViewPlayerWillPlayNotification = @"CTAssetScrollViewPlayerWillPlayNotification";
NSString * const CTAssetScrollViewPlayerWillPauseNotification = @"CTAssetScrollViewPlayerWillPauseNotification";
@interface CTAssetScrollView ()
<UIScrollViewDelegate, UIGestureRecognizerDelegate>
@property (nonatomic, strong) PHAsset *asset;
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, assign) BOOL didLoadPlayerItem;
@property (nonatomic, assign) CGFloat perspectiveZoomScale;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIProgressView *progressView;
@property (nonatomic, strong) UIActivityIndicatorView *activityView;
@property (nonatomic, strong) CTAssetPlayButton *playButton;
@property (nonatomic, strong) CTAssetSelectionButton *selectionButton;
@property (nonatomic, assign) BOOL shouldUpdateConstraints;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetScrollView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_shouldUpdateConstraints = YES;
self.allowsSelection = NO;
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.bouncesZoom = YES;
self.decelerationRate = UIScrollViewDecelerationRateFast;
self.delegate = self;
[self setupViews];
[self addGestureRecognizers];
}
return self;
}
- (void)dealloc
{
[self removePlayerNotificationObserver];
[self removePlayerLoadedTimeRangesObserver];
[self removePlayerRateObserver];
}
#pragma mark - Setup
- (void)setupViews
{
UIImageView *imageView = [UIImageView new];
imageView.isAccessibilityElement = YES;
imageView.accessibilityTraits = UIAccessibilityTraitImage;
self.imageView = imageView;
[self addSubview:self.imageView];
UIProgressView *progressView =
[[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
self.progressView = progressView;
[self addSubview:self.progressView];
UIActivityIndicatorView *activityView =
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityView = activityView;
[self addSubview:self.activityView];
CTAssetPlayButton *playButton = [CTAssetPlayButton newAutoLayoutView];
self.playButton = playButton;
[self addSubview:self.playButton];
CTAssetSelectionButton *selectionButton = [CTAssetSelectionButton newAutoLayoutView];
self.selectionButton = selectionButton;
[self addSubview:self.selectionButton];
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[self updateSelectionButtonIfNeeded];
[self autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self updateProgressConstraints];
[self updateActivityConstraints];
[self updateButtonsConstraints];
self.didSetupConstraints = YES;
}
[self updateContentFrame];
[super updateConstraints];
}
- (void)updateSelectionButtonIfNeeded
{
if (!self.allowsSelection)
{
[self.selectionButton removeFromSuperview];
self.selectionButton = nil;
}
}
- (void)updateProgressConstraints
{
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow forConstraints:^{
[self.progressView autoConstrainAttribute:ALAttributeLeading toAttribute:ALAttributeLeading ofView:self.superview withMultiplier:1 relation:NSLayoutRelationEqual];
[self.progressView autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.superview withMultiplier:1 relation:NSLayoutRelationEqual];
[self.progressView autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.superview withMultiplier:1 relation:NSLayoutRelationEqual];
}];
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
[self.progressView autoConstrainAttribute:ALAttributeLeading toAttribute:ALAttributeLeading ofView:self.imageView withMultiplier:1 relation:NSLayoutRelationGreaterThanOrEqual];
[self.progressView autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.imageView withMultiplier:1 relation:NSLayoutRelationLessThanOrEqual];
[self.progressView autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.imageView withMultiplier:1 relation:NSLayoutRelationLessThanOrEqual];
}];
}
- (void)updateActivityConstraints
{
[self.activityView autoAlignAxis:ALAxisVertical toSameAxisOfView:self.superview];
[self.activityView autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.superview];
}
- (void)updateButtonsConstraints
{
[self.playButton autoAlignAxis:ALAxisVertical toSameAxisOfView:self.superview];
[self.playButton autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.superview];
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow forConstraints:^{
[self.selectionButton autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.superview withOffset:-self.layoutMargins.right relation:NSLayoutRelationEqual];
[self.selectionButton autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.superview withOffset:-self.layoutMargins.bottom relation:NSLayoutRelationEqual];
}];
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
[self.selectionButton autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.imageView withOffset:-self.layoutMargins.right relation:NSLayoutRelationLessThanOrEqual];
[self.selectionButton autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.imageView withOffset:-self.layoutMargins.bottom relation:NSLayoutRelationLessThanOrEqual];
}];
}
- (void)updateContentFrame
{
CGSize boundsSize = self.bounds.size;
CGFloat w = self.zoomScale * self.asset.pixelWidth;
CGFloat h = self.zoomScale * self.asset.pixelHeight;
CGFloat dx = (boundsSize.width - w) / 2.0;
CGFloat dy = (boundsSize.height - h) / 2.0;
self.contentOffset = CGPointZero;
self.imageView.frame = CGRectMake(dx, dy, w, h);
}
#pragma mark - Start/stop loading animation
- (void)startActivityAnimating
{
[self.playButton setHidden:YES];
[self.selectionButton setHidden:YES];
[self.activityView startAnimating];
[self postPlayerWillPlayNotification];
}
- (void)stopActivityAnimating
{
[self.playButton setHidden:NO];
[self.selectionButton setHidden:NO];
[self.activityView stopAnimating];
[self postPlayerWillPauseNotification];
}
#pragma mark - Set progress
- (void)setProgress:(CGFloat)progress
{
#if !defined(CT_APP_EXTENSIONS)
[UIApplication sharedApplication].networkActivityIndicatorVisible = progress < 1;
#endif
[self.progressView setProgress:progress animated:(progress < 1)];
self.progressView.hidden = progress == 1;
}
// To mimic image downloading progress
// as PHImageRequestOptions does not work as expected
- (void)mimicProgress
{
CGFloat progress = self.progressView.progress;
if (progress < 0.95)
{
int lowerbound = progress * 100 + 1;
int upperbound = 95;
int random = lowerbound + arc4random() % (upperbound - lowerbound);
CGFloat randomProgress = random / 100.0f;
[self setProgress:randomProgress];
NSInteger randomDelay = 1 + arc4random() % (3 - 1);
[self performSelector:@selector(mimicProgress) withObject:nil afterDelay:randomDelay];
}
}
#pragma mark - asset size
- (CGSize)assetSize
{
return CGSizeMake(self.asset.pixelWidth, self.asset.pixelHeight);
}
#pragma mark - Bind asset image
- (void)bind:(PHAsset *)asset image:(UIImage *)image requestInfo:(NSDictionary *)info
{
self.asset = asset;
self.imageView.accessibilityLabel = asset.accessibilityLabel;
self.playButton.hidden = [asset ctassetsPickerIsPhoto];
BOOL isDegraded = [info[PHImageResultIsDegradedKey] boolValue];
if (self.image == nil || !isDegraded)
{
BOOL zoom = (!self.image);
self.image = image;
self.imageView.image = image;
if (isDegraded)
[self mimicProgress];
else
[self setProgress:1];
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
[self updateZoomScalesAndZoom:zoom];
}
}
#pragma mark - Bind player item
- (void)bind:(AVPlayerItem *)playerItem requestInfo:(NSDictionary *)info
{
[self unbindPlayerItem];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
CALayer *layer = self.imageView.layer;
[layer addSublayer:playerLayer];
playerLayer.frame = layer.bounds;
self.player = player;
[self addPlayerNotificationObserver];
[self addPlayerLoadedTimeRangesObserver];
}
- (void)unbindPlayerItem
{
[self removePlayerNotificationObserver];
[self removePlayerLoadedTimeRangesObserver];
for (CALayer *layer in self.imageView.layer.sublayers)
[layer removeFromSuperlayer];
self.player = nil;
}
#pragma mark - Upate zoom scales
- (void)updateZoomScalesAndZoom:(BOOL)zoom
{
if (!self.asset)
return;
CGSize assetSize = [self assetSize];
CGSize boundsSize = self.bounds.size;
CGFloat xScale = boundsSize.width / assetSize.width; //scale needed to perfectly fit the image width-wise
CGFloat yScale = boundsSize.height / assetSize.height; //scale needed to perfectly fit the image height-wise
CGFloat minScale = MIN(xScale, yScale);
CGFloat maxScale = 3.0 * minScale;
if ([self.asset ctassetsPickerIsVideo])
{
self.minimumZoomScale = minScale;
self.maximumZoomScale = minScale;
}
else
{
self.minimumZoomScale = minScale;
self.maximumZoomScale = maxScale;
}
// update perspective zoom scale
self.perspectiveZoomScale = (boundsSize.width > boundsSize.height) ? xScale : yScale;
if (zoom)
[self zoomToInitialScale];
}
#pragma mark - Zoom
- (void)zoomToInitialScale
{
if ([self canPerspectiveZoom])
[self zoomToPerspectiveZoomScaleAnimated:NO];
else
[self zoomToMinimumZoomScaleAnimated:NO];
}
- (void)zoomToMinimumZoomScaleAnimated:(BOOL)animated
{
[self setZoomScale:self.minimumZoomScale animated:animated];
}
- (void)zoomToMaximumZoomScaleWithGestureRecognizer:(UITapGestureRecognizer *)recognizer
{
CGRect zoomRect = [self zoomRectWithScale:self.maximumZoomScale withCenter:[recognizer locationInView:recognizer.view]];
self.shouldUpdateConstraints = NO;
[UIView animateWithDuration:0.3 animations:^{
[self zoomToRect:zoomRect animated:NO];
CGRect frame = self.imageView.frame;
frame.origin.x = 0;
frame.origin.y = 0;
self.imageView.frame = frame;
}];
}
#pragma mark - Perspective zoom
- (BOOL)canPerspectiveZoom
{
CGSize assetSize = [self assetSize];
CGSize boundsSize = self.bounds.size;
CGFloat assetRatio = assetSize.width / assetSize.height;
CGFloat boundsRatio = boundsSize.width / boundsSize.height;
// can perform perspective zoom when the difference of aspect ratios is smaller than 20%
return (fabs( (assetRatio - boundsRatio) / boundsRatio ) < 0.2f);
}
- (void)zoomToPerspectiveZoomScaleAnimated:(BOOL)animated;
{
CGRect zoomRect = [self zoomRectWithScale:self.perspectiveZoomScale];
[self zoomToRect:zoomRect animated:animated];
}
- (CGRect)zoomRectWithScale:(CGFloat)scale
{
CGSize targetSize;
targetSize.width = self.bounds.size.width / scale;
targetSize.height = self.bounds.size.height / scale;
CGPoint targetOrigin;
targetOrigin.x = (self.asset.pixelWidth - targetSize.width) / 2.0;
targetOrigin.y = (self.asset.pixelHeight - targetSize.height) / 2.0;
CGRect zoomRect;
zoomRect.origin = targetOrigin;
zoomRect.size = targetSize;
return zoomRect;
}
#pragma mark - Zoom with gesture recognizer
- (void)zoomWithGestureRecognizer:(UITapGestureRecognizer *)recognizer
{
if (self.minimumZoomScale == self.maximumZoomScale)
return;
if ([self canPerspectiveZoom])
{
if ((self.zoomScale >= self.minimumZoomScale && self.zoomScale < self.perspectiveZoomScale) ||
(self.zoomScale <= self.maximumZoomScale && self.zoomScale > self.perspectiveZoomScale))
[self zoomToPerspectiveZoomScaleAnimated:YES];
else
[self zoomToMaximumZoomScaleWithGestureRecognizer:recognizer];
return;
}
if (self.zoomScale < self.maximumZoomScale)
[self zoomToMaximumZoomScaleWithGestureRecognizer:recognizer];
else
[self zoomToMinimumZoomScaleAnimated:YES];
}
- (CGRect)zoomRectWithScale:(CGFloat)scale withCenter:(CGPoint)center
{
center = [self.imageView convertPoint:center fromView:self];
CGRect zoomRect;
zoomRect.size.height = self.imageView.frame.size.height / scale;
zoomRect.size.width = self.imageView.frame.size.width / scale;
zoomRect.origin.x = center.x - ((zoomRect.size.width / 2.0));
zoomRect.origin.y = center.y - ((zoomRect.size.height / 2.0));
return zoomRect;
}
#pragma mark - Gesture recognizers
- (void)addGestureRecognizers
{
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapping:)];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapping:)];
doubleTap.numberOfTapsRequired = 2.0;
[singleTap requireGestureRecognizerToFail:doubleTap];
singleTap.delegate = self;
doubleTap.delegate = self;
[self addGestureRecognizer:singleTap];
[self addGestureRecognizer:doubleTap];
}
#pragma mark - Handle tappings
- (void)handleTapping:(UITapGestureRecognizer *)recognizer
{
[[NSNotificationCenter defaultCenter] postNotificationName:CTAssetScrollViewDidTapNotification object:recognizer];
if (recognizer.numberOfTapsRequired == 2)
[self zoomWithGestureRecognizer:recognizer];
}
#pragma mark - Scroll view delegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
self.shouldUpdateConstraints = YES;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
self.scrollEnabled = self.zoomScale != self.perspectiveZoomScale;
if (self.shouldUpdateConstraints)
{
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
}
#pragma mark - Gesture recognizer delegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return !([touch.view isDescendantOfView:self.playButton] || [touch.view isDescendantOfView:self.selectionButton]);
}
#pragma mark - Notification observer
- (void)addPlayerNotificationObserver
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
}
- (void)removePlayerNotificationObserver
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
}
#pragma mark - Video player item key-value observer
- (void)addPlayerLoadedTimeRangesObserver
{
[self.player addObserver:self
forKeyPath:@"currentItem.loadedTimeRanges"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)removePlayerLoadedTimeRangesObserver
{
@try {
[self.player removeObserver:self forKeyPath:@"currentItem.loadedTimeRanges"];
}
@catch (NSException *exception) {
// do nothing
}
}
- (void)addPlayerRateObserver
{
[self.player addObserver:self
forKeyPath:@"rate"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)removePlayerRateObserver
{
@try {
[self.player removeObserver:self forKeyPath:@"rate"];
}
@catch (NSException *exception) {
// do nothing
}
}
#pragma mark - Video playback Key-Value changed
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.player && [keyPath isEqual:@"currentItem.loadedTimeRanges"])
{
NSArray *timeRanges = change[NSKeyValueChangeNewKey];
if (timeRanges && timeRanges.count)
{
CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
if (CMTIME_COMPARE_INLINE(timeRange.duration, ==, self.player.currentItem.duration))
[self performSelector:@selector(playerDidLoadItem:) withObject:object];
}
}
if (object == self.player && [keyPath isEqual:@"rate"])
{
CGFloat rate = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];
if (rate > 0)
[self performSelector:@selector(playerDidPlay:) withObject:object];
if (rate == 0)
[self performSelector:@selector(playerDidPause:) withObject:object];
}
}
#pragma mark - Notifications
- (void)postPlayerWillPlayNotification
{
[[NSNotificationCenter defaultCenter] postNotificationName:CTAssetScrollViewPlayerWillPlayNotification object:nil];
}
- (void)postPlayerWillPauseNotification
{
[[NSNotificationCenter defaultCenter] postNotificationName:CTAssetScrollViewPlayerWillPauseNotification object:nil];
}
#pragma mark - Playback events
- (void)applicationWillResignActive:(NSNotification *)notification
{
[self pauseVideo];
}
- (void)playerDidPlay:(id)sender
{
[self setProgress:1];
[self.playButton setHidden:YES];
[self.selectionButton setHidden:YES];
[self.activityView stopAnimating];
}
- (void)playerDidPause:(id)sender
{
[self.playButton setHidden:NO];
[self.selectionButton setHidden:NO];
}
- (void)playerDidLoadItem:(id)sender
{
if (!self.didLoadPlayerItem)
{
[self setDidLoadPlayerItem:YES];
[self addPlayerRateObserver];
[self.activityView stopAnimating];
[self playVideo];
}
}
#pragma mark - Playback
- (void)playVideo
{
if (self.didLoadPlayerItem)
{
if (CMTIME_COMPARE_INLINE(self.player.currentTime, == , self.player.currentItem.duration))
[self.player seekToTime:kCMTimeZero];
[self postPlayerWillPlayNotification];
[self.player play];
}
}
- (void)pauseVideo
{
if (self.didLoadPlayerItem)
{
[self postPlayerWillPauseNotification];
[self.player pause];
}
else
{
[self stopActivityAnimating];
[self unbindPlayerItem];
}
}
@end

Просмотреть файл

@ -0,0 +1,31 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetSelectionButton : UIControl
@end

Просмотреть файл

@ -0,0 +1,122 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetSelectionButton.h"
#import "CTAssetCheckmark.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
@interface CTAssetSelectionButton ()
@property (nonatomic, strong) CTAssetCheckmark *checkmark;
@property (nonatomic, strong) UIImageView *backgroundView;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetSelectionButton
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.isAccessibilityElement = YES;
self.accessibilityTraits = UIAccessibilityTraitButton;
[self setupViews];
[self localize];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
// Background
UIImage *backgroundImage = [UIImage ctassetsPickerImageNamed:@"CheckmarkUnselected"];
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:backgroundImage];
backgroundView.userInteractionEnabled = NO;
self.backgroundView = backgroundView;
[self addSubview:self.backgroundView];
// Checkmark
CTAssetCheckmark *checkmark = [CTAssetCheckmark newAutoLayoutView];
checkmark.userInteractionEnabled = NO;
checkmark.hidden = YES;
self.checkmark = checkmark;
[self addSubview:self.checkmark];
}
- (void)localize
{
self.accessibilityLabel = CTAssetsPickerLocalizedString(@"Select", nil);
}
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
CGSize size = [UIImage ctassetsPickerImageNamed:@"CheckmarkUnselected"].size;
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
[self autoSetDimensionsToSize:size];
}];
[self.backgroundView autoCenterInSuperview];
[self.checkmark autoCenterInSuperview];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
#pragma mark - States
- (void)setSelected:(BOOL)selected
{
super.selected = selected;
self.checkmark.hidden = !selected;
self.accessibilityLabel = (selected) ? CTAssetsPickerLocalizedString(@"Deselect", nil) : CTAssetsPickerLocalizedString(@"Select", nil);
}
@end

Просмотреть файл

@ -0,0 +1,99 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
/**
* The label to show selection index.
*/
@interface CTAssetSelectionLabel : UILabel
#pragma mark Customizing Appearance
/**
* @name Customizing Appearance
*/
/**
* Determines whether the label is circular or not. *Deprecated*.
*/
@property (nonatomic, assign, getter=isCircular) BOOL circular UI_APPEARANCE_SELECTOR DEPRECATED_MSG_ATTRIBUTE("Use setCornerRadius: instead.");
/**
* The width of the label's border.
*/
@property (nonatomic, assign) CGFloat borderWidth UI_APPEARANCE_SELECTOR;
/**
* The color of the label's border.
*/
@property (nonatomic, weak) UIColor *borderColor UI_APPEARANCE_SELECTOR;
/**
* To set the size of label.
*
* @param size The size of the label.
*/
- (void)setSize:(CGSize)size UI_APPEARANCE_SELECTOR;
/**
* To set the corner radius of label.
*
* @param cornerRadius The radius to use when drawing rounded corners for the labels background.
*/
- (void)setCornerRadius:(CGFloat)cornerRadius UI_APPEARANCE_SELECTOR;
/**
* To set margin of the label from the edges.
*
* @param margin The margin from the edges.
*
* @see setMargin:forVerticalEdge:horizontalEdge:
*/
- (void)setMargin:(CGFloat)margin UI_APPEARANCE_SELECTOR;
/**
* To set margin of the label from specific edges.
*
* @param margin The margin from the edges.
* @param edgeX The layout attribute respresents vertical edge that the label pins to. Either `NSLayoutAttributeLeft` or `NSLayoutAttributeRight`.
* @param edgeY The layout attribute respresents horizontal edge that the label pins to. Either `NSLayoutAttributeTop` or `NSLayoutAttributeBottom`.
*
* @see setMargin:
*/
- (void)setMargin:(CGFloat)margin forVerticalEdge:(NSLayoutAttribute)edgeX horizontalEdge:(NSLayoutAttribute)edgeY UI_APPEARANCE_SELECTOR;
/**
* To set the text attributes to display the label.
*
* Currently only supports attributes `NSFontAttributeName`, `NSForegroundColorAttributeName` and `NSBackgroundColorAttributeName`.
*
* @param textAttributes The text attributes used to display the label.
*/
- (void)setTextAttributes:(NSDictionary<NSString*, id> *)textAttributes UI_APPEARANCE_SELECTOR;
@end

Просмотреть файл

@ -0,0 +1,219 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetSelectionLabel.h"
#import "CTAssetsPickerDefines.h"
/**
* The label to show selection index.
*/
@interface CTAssetSelectionLabel ()
#pragma mark Managing Auto Layout
/**
* The constraints of the size of the label.
*/
@property (nonatomic, strong) NSArray *sizeConstraints;
/**
* The constraint for pinning the label to vertical edge.
*/
@property (nonatomic, strong) NSLayoutConstraint *verticalConstraint;
/**
* The constraint for pinning the label to horizontal edge.
*/
@property (nonatomic, strong) NSLayoutConstraint *horizontalConstraint;
/**
* Determines whether or not the constraints have been set up.
*/
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetSelectionLabel
#pragma mark Initializing a Label Object
/**
* Designated Initializer
*
* @return an initialized label object
*/
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.textAlignment = NSTextAlignmentCenter;
self.font = CTAssetLabelFont;
self.textColor = CTAssetLabelTextColor;
self.backgroundColor = CTAssetLabelBackgroundColor;
self.layer.borderColor = CTAssetLabelBorderColor.CGColor;
self.layer.masksToBounds = YES;
self.isAccessibilityElement = NO;
}
return self;
}
#pragma mark Customizing Appearance
/**
* The width of the label's border
*/
- (CGFloat)borderWidth
{
return self.layer.borderWidth;
}
- (void)setBorderWidth:(CGFloat)borderWidth
{
self.layer.borderWidth = borderWidth;
}
/**
* The color of the label's border
*/
- (UIColor *)borderColor
{
return [UIColor colorWithCGColor:self.layer.borderColor];
}
- (void)setBorderColor:(UIColor *)borderColor
{
UIColor *color = (borderColor) ? borderColor : CTAssetLabelBorderColor;
self.layer.borderColor = color.CGColor;
}
/**
* To set the size of label.
*
* @param size The size of the label.
*/
- (void)setSize:(CGSize)size
{
if (CGSizeEqualToSize(size, CGSizeZero)) {
size = CTAssetLabelSize;
}
[self removeConstraints:self.sizeConstraints];
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
self.sizeConstraints = [self autoSetDimensionsToSize:size];
}];
}
/**
* To set the size of label.
*
* @param cornerRadius The radius to use when drawing rounded corners for the labels background.
*/
- (void)setCornerRadius:(CGFloat)cornerRadius
{
self.layer.cornerRadius = cornerRadius;
}
/**
* To set margin of the label from the edges.
*
* @param margin The margin from the edges.
*
* @see setMargin:forVerticalEdge:horizontalEdge:
*/
- (void)setMargin:(CGFloat)margin
{
[self setMargin:margin forVerticalEdge:NSLayoutAttributeRight horizontalEdge:NSLayoutAttributeBottom];
}
/**
* To set margin of the label from specific edges.
*
* @param margin The margin from the edges.
* @param edgeX The layout attribute respresents vertical edge that the label pins to. Either `NSLayoutAttributeLeft` or `NSLayoutAttributeRight`.
* @param edgeY The layout attribute respresents horizontal edge that the label pins to. Either `NSLayoutAttributeTop` or `NSLayoutAttributeBottom`.
*
* @see setMargin:
*/
- (void)setMargin:(CGFloat)margin forVerticalEdge:(NSLayoutAttribute)edgeX horizontalEdge:(NSLayoutAttribute)edgeY
{
NSAssert(edgeX == NSLayoutAttributeLeft || edgeX == NSLayoutAttributeRight,
@"Vertical edge must be NSLayoutAttributeLeft or NSLayoutAttributeRight");
NSAssert(edgeY == NSLayoutAttributeTop || edgeY == NSLayoutAttributeBottom,
@"Horizontal edge must be NSLayoutAttributeTop or NSLayoutAttributeBottom");
[self.superview removeConstraints:@[self.verticalConstraint, self.horizontalConstraint]];
self.verticalConstraint = [self autoPinEdgeToSuperviewEdge:(ALEdge)edgeX withInset:margin];
self.horizontalConstraint = [self autoPinEdgeToSuperviewEdge:(ALEdge)edgeY withInset:margin];
}
/**
* To set the text attributes to display the label.
*
* Currently only supports attributes `NSFontAttributeName`, `NSForegroundColorAttributeName` and `NSBackgroundColorAttributeName`.
*
* @param textAttributes The text attributes used to display the label.
*/
- (void)setTextAttributes:(NSDictionary *)textAttributes
{
self.font = (textAttributes) ? textAttributes[NSFontAttributeName] : CTAssetLabelFont;
self.textColor = (textAttributes) ? textAttributes[NSForegroundColorAttributeName] : CTAssetLabelTextColor;
self.backgroundColor = (textAttributes) ? textAttributes[NSBackgroundColorAttributeName] : CTAssetLabelBackgroundColor;
}
#pragma mark Triggering Auto Layout
/**
* Updates constraints of the label.
*/
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
self.sizeConstraints = [self autoSetDimensionsToSize:CTAssetLabelSize];
}];
self.verticalConstraint = [self autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:0];
self.horizontalConstraint = [self autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
@end

Просмотреть файл

@ -0,0 +1,36 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
@interface CTAssetThumbnailOverlay : UIView
- (void)bind:(nullable PHAsset *)asset duration:(nullable NSString *)duration;
- (void)bind:(nullable PHAssetCollection *)assetCollection;
@end

Просмотреть файл

@ -0,0 +1,156 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetThumbnailOverlay.h"
#import "UIImage+CTAssetsPickerController.h"
#import "PHAsset+CTAssetsPickerController.h"
#import "PHAssetCollection+CTAssetsPickerController.h"
@interface CTAssetThumbnailOverlay ()
@property (nonatomic, strong) UIImageView *gradient;
@property (nonatomic, strong) UIImageView *badge;
@property (nonatomic, strong) UILabel *duration;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetThumbnailOverlay
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.opaque = NO;
self.clipsToBounds = YES;
self.isAccessibilityElement = NO;
[self setupViews];
}
return self;
}
#pragma markt - Setup
- (void)setupViews
{
UIImageView *gradient = [UIImageView newAutoLayoutView];
gradient.image = [UIImage ctassetsPickerImageNamed:@"GridGradient"];
self.gradient = gradient;
[self addSubview:self.gradient];
UIImageView *badge = [UIImageView newAutoLayoutView];
badge.tintColor = [UIColor whiteColor];
self.badge = badge;
[self addSubview:self.badge];
UILabel *duration = [UILabel newAutoLayoutView];
duration.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2];
duration.textColor = [UIColor whiteColor];
duration.lineBreakMode = NSLineBreakByTruncatingTail;
duration.layoutMargins = UIEdgeInsetsMake(2.5, 2.5, 2.5, 2.5);
self.duration = duration;
[self addSubview:self.duration];
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[self.gradient autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeTop];
[self.gradient autoSetDimension:ALDimensionHeight toSize:self.gradient.image.size.height];
[self.badge autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:self.badge.layoutMargins.left];
[self.badge autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.badge.layoutMargins.bottom];
[self.duration autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:self.duration.layoutMargins.right];
[self.duration autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.duration.layoutMargins.bottom];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
#pragma - Bind asset and duration
- (void)bind:(PHAsset *)asset duration:(NSString *)duration;
{
self.badge.image = [asset badgeImage];
self.badge.layoutMargins = [self layoutMarginsForAsset:asset];
self.duration.text = duration;
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
- (UIEdgeInsets)layoutMarginsForAsset:(PHAsset *)asset
{
if (asset.ctassetsPickerIsHighFrameRateVideo)
return UIEdgeInsetsMake(2.5, 2.5, 2.5, 2.5);
else if (asset.ctassetsPickerIsTimelapseVideo)
return UIEdgeInsetsMake(2.5, 2.5, 2.5, 2.5);
else if (asset.ctassetsPickerIsVideo)
return UIEdgeInsetsMake(4.5, 4.5, 4.5, 4.5);
else
return UIEdgeInsetsZero;
}
#pragma - Bind asset collection
- (void)bind:(PHAssetCollection *)assetCollection;
{
self.badge.image = [assetCollection badgeImage];
self.badge.layoutMargins = [self layoutMarginsForAssetCollection:assetCollection];
self.duration.text = nil;
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
- (UIEdgeInsets)layoutMarginsForAssetCollection:(PHAssetCollection *)assetCollection
{
return UIEdgeInsetsMake(4.0, 4.0, 4.0, 4.0);
}
@end

Просмотреть файл

@ -0,0 +1,39 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import "CTAssetThumbnailView.h"
@interface CTAssetThumbnailStacks : UIView
@property (nonatomic, assign) CGSize thumbnailSize;
@property (nonatomic, copy, readonly) NSArray<CTAssetThumbnailView*> *thumbnailViews;
@property (nonatomic, assign, readonly) UIEdgeInsets edgeInsets;
- (nonnull CTAssetThumbnailView *)thumbnailAtIndex:(NSUInteger)index;
- (void)setHighlighted:(BOOL)highlighted;
@end

Просмотреть файл

@ -0,0 +1,141 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetThumbnailStacks.h"
#import "CTAssetThumbnailView.h"
@interface CTAssetThumbnailStacks ()
@property (nonatomic, copy) NSArray *thumbnailViews;
@property (nonatomic, assign) UIEdgeInsets edgeInsets;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetThumbnailStacks
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
_edgeInsets = UIEdgeInsetsMake(4.0, 0, 0, 0);
self.opaque = YES;
self.clipsToBounds = YES;
self.isAccessibilityElement = NO;
[self setupViews];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
NSMutableArray *thumbnailViews = [NSMutableArray new];
for (NSUInteger index = 0; index < 3; index++)
{
CTAssetThumbnailView *thumbnailView = [CTAssetThumbnailView newAutoLayoutView];
thumbnailView.showsDuration = NO;
thumbnailView.layer.borderColor = [UIColor whiteColor].CGColor;
thumbnailView.layer.borderWidth = 0.5f;
[thumbnailViews addObject:thumbnailView];
[self insertSubview:thumbnailView atIndex:0];
}
self.thumbnailViews = [NSArray arrayWithArray:thumbnailViews];
}
#pragma markt - Setters
- (void)setThumbnailSize:(CGSize)thumbnailSize
{
_thumbnailSize = thumbnailSize;
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
for (NSUInteger index = 0; index < self.thumbnailViews.count; index++)
{
CTAssetThumbnailView *thumbnailView = [self thumbnailAtIndex:index];
CGFloat delta = self.edgeInsets.top / 2;
CGSize size = self.thumbnailSize;
size.width -= index * delta * 2;
size.height -= index * delta * 2;
CGFloat inset = (index * delta * 3);
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
[thumbnailView autoSetDimensionsToSize:size];
}];
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
[thumbnailView autoAlignAxisToSuperviewAxis:ALAxisVertical];
[thumbnailView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:inset];
}];
}
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
- (CTAssetThumbnailView *)thumbnailAtIndex:(NSUInteger)index
{
return self.thumbnailViews[index];
}
- (void)setHighlighted:(BOOL)highlighted
{
for (CTAssetThumbnailView *thumbnailView in self.thumbnailViews)
thumbnailView.backgroundColor = CTAssetsPikcerThumbnailBackgroundColor;
}
@end

Просмотреть файл

@ -0,0 +1,38 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
@interface CTAssetThumbnailView : UIView
@property (nonatomic, assign) BOOL showsDuration;
@property (nonatomic, strong, nullable) UIImage *backgroundImage;
- (void)bind:(nullable UIImage *)image asset:(nullable PHAsset *)asset;
- (void)bind:(nullable UIImage *)image assetCollection:(nullable PHAssetCollection *)assetCollection;
@end

Просмотреть файл

@ -0,0 +1,196 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetThumbnailView.h"
#import "CTAssetThumbnailOverlay.h"
#import "PHAsset+CTAssetsPickerController.h"
#import "NSDateFormatter+CTAssetsPickerController.h"
@interface CTAssetThumbnailView ()
@property (nonatomic, strong) CTAssetThumbnailOverlay *overlay;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImageView *backgroundView;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetThumbnailView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
_showsDuration = YES;
self.opaque = YES;
self.clipsToBounds = YES;
self.isAccessibilityElement = NO;
[self setupViews];
}
return self;
}
#pragma markt - Setup
- (void)setupViews
{
self.backgroundColor = CTAssetsPikcerThumbnailBackgroundColor;
UIImageView *backgroundView = [UIImageView new];
backgroundView.contentMode = UIViewContentModeCenter;
backgroundView.tintColor = CTAssetsPikcerThumbnailTintColor;
self.backgroundView = backgroundView;
UIImageView *imageView = [UIImageView new];
imageView.contentMode = UIViewContentModeScaleAspectFill;
self.imageView = imageView;
[self addSubview:self.backgroundView];
[self addSubview:self.imageView];
}
#pragma markt - Setters
- (void)setBackgroundImage:(UIImage *)backgroundImage
{
_backgroundImage = backgroundImage;
self.backgroundView.image = backgroundImage;
}
#pragma markt - Override set bounds
-(void)setBounds:(CGRect)bounds
{
super.bounds = bounds;
self.overlay.frame = bounds;
[self.overlay setNeedsDisplay];
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[self.backgroundView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.imageView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
#pragma - Bind asset and image
- (void)bind:(UIImage *)image asset:(PHAsset *)asset;
{
[self setupOverlayForAsset:asset];
self.imageView.image = image;
self.backgroundView.hidden = (image != nil);
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
- (void)setupOverlayForAsset:(PHAsset *)asset
{
if (asset.ctassetsPickerIsVideo)
{
if (!self.overlay) {
self.overlay = [[CTAssetThumbnailOverlay alloc] initWithFrame:self.bounds];
[self addSubview:self.overlay];
}
NSString *duration = nil;
if (self.showsDuration)
{
NSDateFormatter *df = [NSDateFormatter new];
duration = [df ctassetsPickerStringFromTimeInterval:asset.duration];
}
[self.overlay bind:asset duration:duration];
}
else
{
[self.overlay removeFromSuperview];
self.overlay = nil;
}
}
#pragma - Bind asset collection and image
- (void)bind:(UIImage *)image assetCollection:(PHAssetCollection *)assetCollection;
{
[self setupOverlayForAssetCollection:assetCollection];
self.imageView.image = image;
self.backgroundView.hidden = (image != nil);
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
- (void)setupOverlayForAssetCollection:(PHAssetCollection *)assetCollection
{
if (assetCollection.assetCollectionType == PHAssetCollectionTypeSmartAlbum &&
assetCollection.assetCollectionSubtype != PHAssetCollectionSubtypeSmartAlbumAllHidden)
{
if (!self.overlay) {
self.overlay = [[CTAssetThumbnailOverlay alloc] initWithFrame:self.bounds];
[self addSubview:self.overlay];
}
[self.overlay bind:assetCollection];
}
else
{
[self.overlay removeFromSuperview];
self.overlay = nil;
}
}
@end

Просмотреть файл

@ -0,0 +1,41 @@
/*
MIT License (MIT)
Copyright (c) 2013 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetsGridSelectedView : UIView
@property (nonatomic, assign) BOOL showsSelectionIndex;
@property (nonatomic, assign) NSUInteger selectionIndex;
@property (nonatomic, weak, nullable) UIColor *selectedBackgroundColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign) CGFloat borderWidth UI_APPEARANCE_SELECTOR;
@property (nonatomic, weak, nullable) UIFont *font UI_APPEARANCE_SELECTOR DEPRECATED_MSG_ATTRIBUTE("Use setTextAttributes: of CTAssetSelectionLabel instead.");
@property (nonatomic, weak, nullable) UIColor *textColor UI_APPEARANCE_SELECTOR DEPRECATED_MSG_ATTRIBUTE("Use setTextAttributes: of CTAssetSelectionLabel instead.");
@end

Просмотреть файл

@ -0,0 +1,142 @@
/*
MIT License (MIT)
Copyright (c) 2013 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetsGridSelectedView.h"
#import "CTAssetCheckmark.h"
#import "CTAssetSelectionLabel.h"
@interface CTAssetsGridSelectedView ()
@property (nonatomic, strong) CTAssetCheckmark *checkmark;
@property (nonatomic, strong) CTAssetSelectionLabel *selectionIndexLabel;
@end
@implementation CTAssetsGridSelectedView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self setupViews];
self.showsSelectionIndex = NO;
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
self.backgroundColor = CTAssetsGridSelectedViewBackgroundColor;
self.layer.borderColor = CTAssetsGridSelectedViewTintColor.CGColor;
CTAssetCheckmark *checkmark = [CTAssetCheckmark newAutoLayoutView];
self.checkmark = checkmark;
[self addSubview:checkmark];
CTAssetSelectionLabel *selectionIndexLabel = [CTAssetSelectionLabel newAutoLayoutView];
self.selectionIndexLabel = selectionIndexLabel;
[self addSubview:self.selectionIndexLabel];
}
#pragma mark - Accessors
- (void)setShowsSelectionIndex:(BOOL)showsSelectionIndex
{
_showsSelectionIndex = showsSelectionIndex;
if (showsSelectionIndex)
{
self.checkmark.hidden = YES;
self.selectionIndexLabel.hidden = NO;
}
else
{
self.checkmark.hidden = NO;
self.selectionIndexLabel.hidden = YES;
}
}
- (void)setSelectionIndex:(NSUInteger)selectionIndex
{
_selectionIndex = selectionIndex;
self.selectionIndexLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)(selectionIndex + 1)];
}
#pragma mark - Apperance
- (UIColor *)selectedBackgroundColor
{
return self.backgroundColor;
}
- (void)setSelectedBackgroundColor:(UIColor *)backgroundColor
{
UIColor *color = (backgroundColor) ? backgroundColor : CTAssetsGridSelectedViewBackgroundColor;
self.backgroundColor = color;
}
- (CGFloat)borderWidth
{
return self.layer.borderWidth;
}
- (void)setBorderWidth:(CGFloat)borderWidth
{
self.layer.borderWidth = borderWidth;
}
- (void)setTintColor:(UIColor *)tintColor
{
UIColor *color = (tintColor) ? tintColor : CTAssetsGridSelectedViewTintColor;
self.layer.borderColor = color.CGColor;
}
#pragma mark - Accessibility Label
- (NSString *)accessibilityLabel
{
return self.selectionIndexLabel.text;
}
@end

Просмотреть файл

@ -0,0 +1,33 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetsGridView : UIView
@property (nonatomic, weak, nullable) UIColor *gridBackgroundColor UI_APPEARANCE_SELECTOR;
@end

Просмотреть файл

@ -0,0 +1,86 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetsGridView.h"
@interface CTAssetsGridView ()
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetsGridView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self setupViews];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
self.backgroundColor = CTAssetsGridViewBackgroundColor;
}
#pragma mark - Apperance
- (UIColor *)gridBackgroundColor
{
return self.backgroundColor;
}
- (void)setGridBackgroundColor:(UIColor *)backgroundColor
{
UIColor *color = (backgroundColor) ? (backgroundColor) : CTAssetsGridViewBackgroundColor;
self.backgroundColor = color;
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[self autoPinEdgesToSuperviewEdges];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
@end

Просмотреть файл

@ -0,0 +1,44 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
#import "CTAssetThumbnailView.h"
@interface CTAssetsGridViewCell : UICollectionViewCell
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
@property (nonatomic, assign) BOOL showsSelectionIndex;
@property (nonatomic, assign) NSUInteger selectionIndex;
@property (nonatomic, weak, nullable) UIColor *disabledColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, weak, nullable) UIColor *highlightedColor UI_APPEARANCE_SELECTOR;
- (void)bind:(nonnull PHAsset *)asset;
@end

Просмотреть файл

@ -0,0 +1,204 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetsGridViewCell.h"
#import "CTAssetsGridSelectedView.h"
#import "PHAsset+CTAssetsPickerController.h"
#import "NSDateFormatter+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
@interface CTAssetsGridViewCell ()
@property (nonatomic, strong) PHAsset *asset;
@property (nonatomic, strong) UIImageView *disabledImageView;
@property (nonatomic, strong) UIView *disabledView;
@property (nonatomic, strong) UIView *highlightedView;
@property (nonatomic, strong) CTAssetsGridSelectedView *selectedView;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetsGridViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.opaque = YES;
self.isAccessibilityElement = YES;
self.accessibilityTraits = UIAccessibilityTraitImage;
self.enabled = YES;
self.showsSelectionIndex = NO;
[self setupViews];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
CTAssetThumbnailView *thumbnailView = [CTAssetThumbnailView newAutoLayoutView];
self.backgroundView = thumbnailView;
UIImage *disabledImage = [UIImage ctassetsPickerImageNamed:@"GridDisabledAsset"];
disabledImage = [disabledImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImageView *disabledImageView = [[UIImageView alloc] initWithImage:disabledImage];
disabledImageView.tintColor = CTAssetsPikcerThumbnailTintColor;
self.disabledImageView = disabledImageView;
UIView *disabledView = [UIView newAutoLayoutView];
disabledView.backgroundColor = CTAssetsGridViewCellDisabledColor;
disabledView.hidden = YES;
[disabledView addSubview:self.disabledImageView];
self.disabledView = disabledView;
[self addSubview:self.disabledView];
UIView *highlightedView = [UIView newAutoLayoutView];
highlightedView.backgroundColor = CTAssetsGridViewCellHighlightedColor;
highlightedView.hidden = YES;
self.highlightedView = highlightedView;
[self addSubview:self.highlightedView];
CTAssetsGridSelectedView *selectedView = [CTAssetsGridSelectedView newAutoLayoutView];
selectedView.hidden = YES;
self.selectedView = selectedView;
[self addSubview:self.selectedView];
}
#pragma mark - Apperance
- (UIColor *)disabledColor
{
return self.disabledView.backgroundColor;
}
- (void)setDisabledColor:(UIColor *)disabledColor
{
UIColor *color = (disabledColor) ? disabledColor : CTAssetsGridViewCellDisabledColor;
self.disabledView.backgroundColor = color;
}
- (UIColor *)highlightedColor
{
return self.highlightedView.backgroundColor;
}
- (void)setHighlightedColor:(UIColor *)highlightedColor
{
UIColor *color = (highlightedColor) ? highlightedColor : CTAssetsGridViewCellHighlightedColor;
self.highlightedView.backgroundColor = color;
}
#pragma mark - Accessors
- (void)setEnabled:(BOOL)enabled
{
_enabled = enabled;
self.disabledView.hidden = enabled;
}
- (void)setHighlighted:(BOOL)highlighted
{
super.highlighted = highlighted;
self.highlightedView.hidden = !highlighted;
}
- (void)setSelected:(BOOL)selected
{
super.selected = selected;
self.selectedView.hidden = !selected;
}
- (void)setShowsSelectionIndex:(BOOL)showsSelectionIndex
{
_showsSelectionIndex = showsSelectionIndex;
self.selectedView.showsSelectionIndex = showsSelectionIndex;
}
- (void)setSelectionIndex:(NSUInteger)selectionIndex
{
_selectionIndex = selectionIndex;
self.selectedView.selectionIndex = selectionIndex;
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{
[self.backgroundView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.disabledView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.highlightedView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
[self.selectedView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
}];
[self.disabledImageView autoCenterInSuperview];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
- (void)bind:(PHAsset *)asset
{
self.asset = asset;
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
#pragma mark - Accessibility Label
- (NSString *)accessibilityLabel
{
if (self.selectedView.accessibilityLabel)
return [NSString stringWithFormat:@"%@, %@", self.selectedView.accessibilityLabel, self.asset.accessibilityLabel];
else
return self.asset.accessibilityLabel;
}
@end

Просмотреть файл

@ -0,0 +1,50 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
@class CTAssetsGridViewController;
@protocol CTAssetsGridViewControllerDelegate <NSObject>
- (void)assetsGridViewController:(nonnull CTAssetsGridViewController *)picker photoLibraryDidChangeForAssetCollection:(nonnull PHAssetCollection *)assetCollection;
@end
@interface CTAssetsGridViewController : UICollectionViewController
@property (nonatomic, weak) id<CTAssetsGridViewControllerDelegate> delegate;
@property (nonatomic, strong, nonnull) PHAssetCollection *assetCollection;
@end

Просмотреть файл

@ -0,0 +1,802 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import "CTAssetsPickerDefines.h"
#import "CTAssetsPickerController.h"
#import "CTAssetsPickerController+Internal.h"
#import "CTAssetsGridViewController.h"
#import "CTAssetsGridView.h"
#import "CTAssetsGridViewLayout.h"
#import "CTAssetsGridViewCell.h"
#import "CTAssetsGridViewFooter.h"
#import "CTAssetsPickerNoAssetsView.h"
#import "CTAssetsPageViewController.h"
#import "CTAssetsPageViewController+Internal.h"
#import "CTAssetsViewControllerTransition.h"
#import "UICollectionView+CTAssetsPickerController.h"
#import "NSIndexSet+CTAssetsPickerController.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "PHImageManager+CTAssetsPickerController.h"
NSString * const CTAssetsGridViewCellIdentifier = @"CTAssetsGridViewCellIdentifier";
NSString * const CTAssetsGridViewFooterIdentifier = @"CTAssetsGridViewFooterIdentifier";
@interface CTAssetsGridViewController ()
<PHPhotoLibraryChangeObserver>
@property (nonatomic, weak) CTAssetsPickerController *picker;
@property (nonatomic, strong) PHFetchResult *fetchResult;
@property (nonatomic, strong) PHCachingImageManager *imageManager;
@property (nonatomic, assign) CGRect previousPreheatRect;
@property (nonatomic, assign) CGRect previousBounds;
@property (nonatomic, strong) CTAssetsGridViewFooter *footer;
@property (nonatomic, strong) CTAssetsPickerNoAssetsView *noAssetsView;
@property (nonatomic, assign) BOOL didLayoutSubviews;
@end
@implementation CTAssetsGridViewController
- (instancetype)init
{
CTAssetsGridViewLayout *layout = [CTAssetsGridViewLayout new];
if (self = [super initWithCollectionViewLayout:layout])
{
_imageManager = [PHCachingImageManager new];
self.extendedLayoutIncludesOpaqueBars = YES;
self.collectionView.allowsMultipleSelection = YES;
[self.collectionView registerClass:CTAssetsGridViewCell.class
forCellWithReuseIdentifier:CTAssetsGridViewCellIdentifier];
[self.collectionView registerClass:CTAssetsGridViewFooter.class
forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:CTAssetsGridViewFooterIdentifier];
[self addNotificationObserver];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
[self registerChangeObserver];
[self addGestureRecognizer];
[self addNotificationObserver];
[self resetCachedAssetImages];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupButtons];
[self setupAssets];
[self updateTitle:self.picker.selectedAssets];
[self updateButton:self.picker.selectedAssets];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self updateCachedAssetImages];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (!CGRectEqualToRect(self.view.bounds, self.previousBounds))
{
[self updateCollectionViewLayout];
self.previousBounds = self.view.bounds;
}
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (!self.didLayoutSubviews && self.fetchResult.count > 0)
{
[self scrollToBottomIfNeeded];
self.didLayoutSubviews = YES;
}
}
- (void)dealloc
{
[self unregisterChangeObserver];
[self removeNotificationObserver];
}
#pragma mark - Accessors
- (CTAssetsPickerController *)picker
{
return (CTAssetsPickerController *)self.splitViewController.parentViewController;
}
- (PHAsset *)assetAtIndexPath:(NSIndexPath *)indexPath
{
return (self.fetchResult.count > 0) ? self.fetchResult[indexPath.item] : nil;
}
#pragma mark - Setup
- (void)setupViews
{
self.collectionView.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
CTAssetsGridView *gridView = [CTAssetsGridView new];
[self.view insertSubview:gridView atIndex:0];
[self.view setNeedsUpdateConstraints];
}
- (void)setupButtons
{
if (self.navigationItem.rightBarButtonItem == nil)
{
NSString *title = (self.picker.doneButtonTitle) ?
self.picker.doneButtonTitle : CTAssetsPickerLocalizedString(@"Done", nil);
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:title
style:UIBarButtonItemStyleDone
target:self.picker
action:@selector(finishPickingAssets:)];
}
}
- (void)setupAssets
{
PHFetchResult *fetchResult =
[PHAsset fetchAssetsInAssetCollection:self.assetCollection
options:self.picker.assetsFetchOptions];
self.fetchResult = fetchResult;
[self reloadData];
}
#pragma mark - Collection view layout
- (void)updateCollectionViewLayout
{
UITraitCollection *trait = self.traitCollection;
CGSize contentSize = self.view.bounds.size;
UICollectionViewLayout *layout;
NSArray *attributes = [self.collectionView.collectionViewLayout layoutAttributesForElementsInRect:self.collectionView.bounds];
UICollectionViewLayoutAttributes *attr = (UICollectionViewLayoutAttributes*)attributes.firstObject;
// new content size should be at least of first item size, else ignoring
if (contentSize.width < attr.size.width || contentSize.height < attr.size.height) {
return;
}
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:collectionViewLayoutForContentSize:traitCollection:)]) {
layout = [self.picker.delegate assetsPickerController:self.picker collectionViewLayoutForContentSize:contentSize traitCollection:trait];
} else {
layout = [[CTAssetsGridViewLayout alloc] initWithContentSize:contentSize traitCollection:trait];
}
__weak CTAssetsGridViewController *weakSelf = self;
[self.collectionView setCollectionViewLayout:layout animated:NO completion:^(BOOL finished){
[weakSelf.collectionView reloadItemsAtIndexPaths:[weakSelf.collectionView indexPathsForVisibleItems]];
}];
}
#pragma mark - Scroll to bottom
- (void)scrollToBottomIfNeeded
{
BOOL shouldScrollToBottom;
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldScrollToBottomForAssetCollection:)])
shouldScrollToBottom = [self.picker.delegate assetsPickerController:self.picker shouldScrollToBottomForAssetCollection:self.assetCollection];
else
shouldScrollToBottom = YES;
if (shouldScrollToBottom)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.fetchResult.count-1 inSection:0];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
}
}
#pragma mark - Notifications
- (void)addNotificationObserver
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(assetsPickerSelectedAssetsDidChange:)
name:CTAssetsPickerSelectedAssetsDidChangeNotification
object:nil];
[center addObserver:self
selector:@selector(assetsPickerDidSelectAsset:)
name:CTAssetsPickerDidSelectAssetNotification
object:nil];
[center addObserver:self
selector:@selector(assetsPickerDidDeselectAsset:)
name:CTAssetsPickerDidDeselectAssetNotification
object:nil];
}
- (void)removeNotificationObserver
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:CTAssetsPickerSelectedAssetsDidChangeNotification object:nil];
[center removeObserver:self name:CTAssetsPickerDidSelectAssetNotification object:nil];
[center removeObserver:self name:CTAssetsPickerDidDeselectAssetNotification object:nil];
}
#pragma mark - Photo library change observer
- (void)registerChangeObserver
{
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
}
- (void)unregisterChangeObserver
{
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
}
#pragma mark - Photo library changed
- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
dispatch_async(dispatch_get_main_queue(), ^{
PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:self.fetchResult];
if (changeDetails)
{
self.fetchResult = changeDetails.fetchResultAfterChanges;
UICollectionView *collectionView = self.collectionView;
if (!changeDetails.hasIncrementalChanges || changeDetails.hasMoves)
{
[collectionView reloadData];
[self resetCachedAssetImages];
}
else
{
NSArray *removedPaths;
NSArray *insertedPaths;
NSArray *changedPaths;
NSIndexSet *removedIndexes = changeDetails.removedIndexes;
removedPaths = [removedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
NSIndexSet *insertedIndexes = changeDetails.insertedIndexes;
insertedPaths = [insertedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
NSIndexSet *changedIndexes = changeDetails.changedIndexes;
changedPaths = [changedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
BOOL shouldReload = NO;
if (changedPaths != nil && removedPaths != nil)
{
for (NSIndexPath *changedPath in changedPaths)
{
if ([removedPaths containsObject:changedPath])
{
shouldReload = YES;
break;
}
}
}
if (removedPaths.lastObject && ((NSIndexPath *)removedPaths.lastObject).item >= self.fetchResult.count)
{
shouldReload = YES;
}
if (shouldReload)
{
[collectionView reloadData];
}
else
{
// if we have incremental diffs, tell the collection view to animate insertions and deletions
[collectionView performBatchUpdates:^{
if (removedPaths.count)
{
[collectionView deleteItemsAtIndexPaths:[removedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0]];
}
if (insertedPaths.count)
{
[collectionView insertItemsAtIndexPaths:[insertedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0]];
}
if (changedPaths.count)
{
[collectionView reloadItemsAtIndexPaths:[changedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0] ];
}
} completion:^(BOOL finished){
if (finished)
[self resetCachedAssetImages];
}];
}
}
[self.footer bind:self.fetchResult];
if (self.fetchResult.count == 0)
[self showNoAssets];
else
[self hideNoAssets];
}
if ([self.delegate respondsToSelector:@selector(assetsGridViewController:photoLibraryDidChangeForAssetCollection:)])
[self.delegate assetsGridViewController:self photoLibraryDidChangeForAssetCollection:self.assetCollection];
});
}
#pragma mark - Selected assets changed
- (void)assetsPickerSelectedAssetsDidChange:(NSNotification *)notification
{
NSArray *selectedAssets = (NSArray *)notification.object;
[self updateTitle:selectedAssets];
[self updateButton:selectedAssets];
}
- (void)updateTitle:(NSArray *)selectedAssets
{
if (selectedAssets.count > 0)
self.title = self.picker.selectedAssetsString;
else
self.title = self.assetCollection.localizedTitle;
}
- (void)updateButton:(NSArray *)selectedAssets
{
if (self.picker.alwaysEnableDoneButton)
self.navigationItem.rightBarButtonItem.enabled = YES;
else
self.navigationItem.rightBarButtonItem.enabled = (self.picker.selectedAssets.count > 0);
}
#pragma mark - Did de/select asset notifications
- (void)assetsPickerDidSelectAsset:(NSNotification *)notification
{
PHAsset *asset = (PHAsset *)notification.object;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self.fetchResult indexOfObject:asset] inSection:0];
[self.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
[self updateSelectionOrderLabels];
}
- (void)assetsPickerDidDeselectAsset:(NSNotification *)notification
{
PHAsset *asset = (PHAsset *)notification.object;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self.fetchResult indexOfObject:asset] inSection:0];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self updateSelectionOrderLabels];
}
#pragma mark - Update Selection Order Labels
- (void)updateSelectionOrderLabels
{
for (NSIndexPath *indexPath in [self.collectionView indexPathsForSelectedItems])
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
CTAssetsGridViewCell *cell = (CTAssetsGridViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
cell.selectionIndex = [self.picker.selectedAssets indexOfObject:asset];
}
}
#pragma mark - Gesture recognizer
- (void)addGestureRecognizer
{
UILongPressGestureRecognizer *longPress =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(pushPageViewController:)];
[self.collectionView addGestureRecognizer:longPress];
}
#pragma mark - Push assets page view controller
- (void)pushPageViewController:(UILongPressGestureRecognizer *)longPress
{
if (longPress.state == UIGestureRecognizerStateBegan)
{
CGPoint point = [longPress locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point];
CTAssetsPageViewController *vc = [[CTAssetsPageViewController alloc] initWithFetchResult:self.fetchResult];
vc.allowsSelection = YES;
vc.pageIndex = indexPath.item;
[self.navigationController pushViewController:vc animated:YES];
}
}
#pragma mark - Reload data
- (void)reloadData
{
if (self.fetchResult.count > 0)
{
[self hideNoAssets];
[self.collectionView reloadData];
}
else
{
[self showNoAssets];
}
}
#pragma mark - Asset images caching
- (void)resetCachedAssetImages
{
[self.imageManager stopCachingImagesForAllAssets];
self.previousPreheatRect = CGRectZero;
}
- (void)updateCachedAssetImages
{
BOOL isViewVisible = [self isViewLoaded] && self.view.window != nil;
if (!isViewVisible)
return;
// The preheat window is twice the height of the visible rect
CGRect preheatRect = self.collectionView.bounds;
preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
// If scrolled by a "reasonable" amount...
CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f)
{
// Compute the assets to start caching and to stop caching.
NSMutableArray *addedIndexPaths = [NSMutableArray array];
NSMutableArray *removedIndexPaths = [NSMutableArray array];
[self computeDifferenceBetweenRect:self.previousPreheatRect
andRect:preheatRect
removedHandler:^(CGRect removedRect) {
NSArray *indexPaths = [self.collectionView ctassetsPickerIndexPathsForElementsInRect:removedRect];
[removedIndexPaths addObjectsFromArray:indexPaths];
} addedHandler:^(CGRect addedRect) {
NSArray *indexPaths = [self.collectionView ctassetsPickerIndexPathsForElementsInRect:addedRect];
[addedIndexPaths addObjectsFromArray:indexPaths];
}];
[self startCachingThumbnailsForIndexPaths:addedIndexPaths];
[self stopCachingThumbnailsForIndexPaths:removedIndexPaths];
self.previousPreheatRect = preheatRect;
}
}
- (void)startCachingThumbnailsForIndexPaths:(NSArray *)indexPaths
{
for (NSIndexPath *indexPath in indexPaths)
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
if (!asset) break;
UICollectionViewLayoutAttributes *attributes =
[self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
[self.imageManager startCachingImagesForAssets:@[asset]
targetSize:targetSize
contentMode:PHImageContentModeAspectFill
options:self.picker.thumbnailRequestOptions];
}
}
- (void)stopCachingThumbnailsForIndexPaths:(NSArray *)indexPaths
{
for (NSIndexPath *indexPath in indexPaths)
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
if (!asset) break;
UICollectionViewLayoutAttributes *attributes =
[self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
[self.imageManager stopCachingImagesForAssets:@[asset]
targetSize:targetSize
contentMode:PHImageContentModeAspectFill
options:self.picker.thumbnailRequestOptions];
}
}
- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
{
if (CGRectIntersectsRect(newRect, oldRect)) {
CGFloat oldMaxY = CGRectGetMaxY(oldRect);
CGFloat oldMinY = CGRectGetMinY(oldRect);
CGFloat newMaxY = CGRectGetMaxY(newRect);
CGFloat newMinY = CGRectGetMinY(newRect);
if (newMaxY > oldMaxY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
addedHandler(rectToAdd);
}
if (oldMinY > newMinY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
addedHandler(rectToAdd);
}
if (newMaxY < oldMaxY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
removedHandler(rectToRemove);
}
if (oldMinY < newMinY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
removedHandler(rectToRemove);
}
} else {
addedHandler(newRect);
removedHandler(oldRect);
}
}
#pragma mark - Scroll view delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self updateCachedAssetImages];
}
#pragma mark - No assets
- (void)showNoAssets
{
CTAssetsPickerNoAssetsView *view = [CTAssetsPickerNoAssetsView new];
[self.view addSubview:view];
[view setNeedsUpdateConstraints];
[view updateConstraintsIfNeeded];
self.noAssetsView = view;
}
- (void)hideNoAssets
{
if (self.noAssetsView)
{
[self.noAssetsView removeFromSuperview];
self.noAssetsView = nil;
}
}
#pragma mark - Collection view data source
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.fetchResult.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CTAssetsGridViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:CTAssetsGridViewCellIdentifier
forIndexPath:indexPath];
PHAsset *asset = [self assetAtIndexPath:indexPath];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)])
cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
else
cell.enabled = YES;
cell.showsSelectionIndex = self.picker.showsSelectionIndex;
// XXX
// Setting `selected` property blocks further deselection.
// Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
if ([self.picker.selectedAssets containsObject:asset])
{
cell.selected = YES;
cell.selectionIndex = [self.picker.selectedAssets indexOfObject:asset];
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
[cell bind:asset];
UICollectionViewLayoutAttributes *attributes =
[collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
[self requestThumbnailForCell:cell targetSize:targetSize asset:asset];
return cell;
}
- (void)requestThumbnailForCell:(CTAssetsGridViewCell *)cell targetSize:(CGSize)targetSize asset:(PHAsset *)asset
{
NSInteger tag = cell.tag + 1;
cell.tag = tag;
[self.imageManager ctassetsPickerRequestImageForAsset:asset
targetSize:targetSize
contentMode:PHImageContentModeAspectFill
options:self.picker.thumbnailRequestOptions
resultHandler:^(UIImage *image, NSDictionary *info){
// Only update the image if the cell tag hasn't changed. Otherwise, the cell has been re-used.
if (cell.tag == tag)
[(CTAssetThumbnailView *)cell.backgroundView bind:image asset:asset];
}];
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
CTAssetsGridViewFooter *footer =
[collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:CTAssetsGridViewFooterIdentifier
forIndexPath:indexPath];
[footer bind:self.fetchResult];
self.footer = footer;
return footer;
}
#pragma mark - Collection view delegate
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
CTAssetsGridViewCell *cell = (CTAssetsGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
if (!cell.isEnabled)
return NO;
else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)])
return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
else
return YES;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
[self.picker selectAsset:asset];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)])
[self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)])
return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
else
return YES;
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
[self.picker deselectAsset:asset];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)])
[self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)])
return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
else
return YES;
}
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)])
[self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
}
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = [self assetAtIndexPath:indexPath];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)])
[self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
}
@end

Просмотреть файл

@ -0,0 +1,38 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
@interface CTAssetsGridViewFooter : UICollectionReusableView
@property (nonatomic, weak, nullable) UIFont *font UI_APPEARANCE_SELECTOR;
@property (nonatomic, weak, nullable) UIColor *textColor UI_APPEARANCE_SELECTOR;
- (void)bind:(nonnull PHFetchResult *)result;
@end

Просмотреть файл

@ -0,0 +1,146 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetsGridViewFooter.h"
#import "NSNumberFormatter+CTAssetsPickerController.h"
#import "NSBundle+CTAssetsPickerController.h"
@interface CTAssetsGridViewFooter ()
@property (nonatomic, strong) UILabel *label;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetsGridViewFooter
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self setupViews];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
UILabel *label = [UILabel newAutoLayoutView];
label.textAlignment = NSTextAlignmentCenter;
label.font = CTAssetsGridViewFooterFont;
label.textColor = CTAssetsGridViewFooterTextColor;
self.label = label;
[self addSubview:self.label];
}
#pragma mark - Appearance
- (UIFont *)font
{
return self.label.font;
}
- (void)setFont:(UIFont *)font
{
UIFont *labelFont = (font) ? font : CTAssetsGridViewFooterFont;
self.label.font = labelFont;
}
- (UIColor *)textColor
{
return self.label.textColor;
}
- (void)setTextColor:(UIColor *)textColor
{
UIColor *color = (textColor) ? textColor : CTAssetsGridViewFooterTextColor;
self.label.textColor = color;
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[self.label autoPinEdgesToSuperviewMargins];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
- (void)bind:(PHFetchResult *)result
{
NSNumberFormatter *nf = [NSNumberFormatter new];
NSString *numberOfVideos = @"";
NSString *numberOfPhotos = @"";
NSUInteger videoCount = [result countOfAssetsWithMediaType:PHAssetMediaTypeVideo];
NSUInteger photoCount = [result countOfAssetsWithMediaType:PHAssetMediaTypeImage];
if (videoCount > 0)
numberOfVideos = [nf ctassetsPickerStringFromAssetsCount:videoCount];
if (photoCount > 0)
numberOfPhotos = [nf ctassetsPickerStringFromAssetsCount:photoCount];
if (photoCount > 0 && videoCount > 0)
self.label.text = [NSString stringWithFormat:CTAssetsPickerLocalizedString(@"%@ Photos, %@ Videos", nil), numberOfPhotos, numberOfVideos];
else if (photoCount > 0 && videoCount <= 0)
self.label.text = [NSString stringWithFormat:CTAssetsPickerLocalizedString(@"%@ Photos", nil), numberOfPhotos];
else if (photoCount <= 0 && videoCount > 0)
self.label.text = [NSString stringWithFormat:CTAssetsPickerLocalizedString(@"%@ Videos", nil), numberOfVideos];
else
self.label.text = @"";
self.hidden = (result.count == 0);
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
@end

Просмотреть файл

@ -0,0 +1,33 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetsGridViewLayout : UICollectionViewFlowLayout
- (instancetype)initWithContentSize:(CGSize)contentSize traitCollection:(UITraitCollection *)traits;
@end

Просмотреть файл

@ -0,0 +1,109 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import "CTAssetsGridViewLayout.h"
@implementation CTAssetsGridViewLayout
- (instancetype)initWithContentSize:(CGSize)contentSize traitCollection:(UITraitCollection *)traits
{
if (self = [super init])
{
CGFloat scale = traits.displayScale;
NSInteger numberOfColumns = [self numberOfColumnsForTraitCollection:traits];
CGFloat onePixel = (scale == 3.0) ? (2.0 / scale) : (1.0 / scale);
// spacing is as small as possible
self.minimumInteritemSpacing = onePixel;
self.minimumLineSpacing = onePixel;
// total spaces between items (in pixel)
CGFloat spaces = self.minimumInteritemSpacing * (numberOfColumns - 1);
// item length (in pixel)
CGFloat length = (scale * (contentSize.width - spaces)) / numberOfColumns;
// remaining spaces (in pixel) after rounding the length to integer
CGFloat insets = (length - floor(length)) * numberOfColumns;
// round the length to integer (in pixel)
length = floor(length);
// divide insets to two
CGFloat left = insets / 2;
CGFloat right = insets / 2;
// adjust if insets is odd
if (fmodf(insets, 2.0) == 1.0f)
{
left -= 0.5;
right += 0.5;
}
// left / right insets (in point, 2 decimal)
left = floorf(left / scale * 100) / 100;
right = floorf(right / scale * 100) / 100;
// item length (in point, 2 decimal)
length = floorf(length / scale * 100) / 100;
self.sectionInset = UIEdgeInsetsMake(0, left, 0, right);
self.itemSize = CGSizeMake(length, length);
self.footerReferenceSize = CGSizeMake(contentSize.width, floor(length * 2/3));
}
return self;
}
- (NSInteger)numberOfColumnsForTraitCollection:(UITraitCollection *)traits
{
switch (traits.userInterfaceIdiom) {
case UIUserInterfaceIdiomPad:
{
return 6;
break;
}
case UIUserInterfaceIdiomPhone:
{
// iPhone 6+ landscape
if (traits.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
return 4;
// iPhone landscape
else if (traits.verticalSizeClass == UIUserInterfaceSizeClassCompact)
return 6;
// iPhone portrait
else
return 4;
break;
}
default:
return 4;
break;
}
}
@end

Просмотреть файл

@ -0,0 +1,31 @@
/*
MIT License (MIT)
Copyright (c) 2015 bawn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetsNavigationController : UINavigationController
@end

Просмотреть файл

@ -0,0 +1,119 @@
/*
MIT License (MIT)
Copyright (c) 2015 bawn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import "CTAssetsNavigationController.h"
@interface CTAssetsNavigationController ()<UIGestureRecognizerDelegate, UINavigationControllerDelegate>
@end
@implementation CTAssetsNavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
__weak CTAssetsNavigationController<UIGestureRecognizerDelegate> *weakSelf = self;
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
{
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ( [self respondsToSelector:@selector(interactivePopGestureRecognizer)] && animated == YES )
{
self.interactivePopGestureRecognizer.enabled = NO;
}
[super pushViewController:viewController animated:animated];
}
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
if ( [self respondsToSelector:@selector(interactivePopGestureRecognizer)] && animated == YES )
{
self.interactivePopGestureRecognizer.enabled = NO;
}
return [super popToRootViewControllerAnimated:animated];
}
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if( [self respondsToSelector:@selector(interactivePopGestureRecognizer)] )
{
self.interactivePopGestureRecognizer.enabled = NO;
}
return [super popToViewController:viewController animated:animated];
}
#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
{
self.interactivePopGestureRecognizer.enabled = YES;
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ( gestureRecognizer == self.interactivePopGestureRecognizer )
{
if ( self.viewControllers.count < 2 || self.visibleViewController == self.viewControllers[0] )
{
return NO;
}
}
return YES;
}
@end

Просмотреть файл

@ -0,0 +1,37 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetsPageView : UIView
@property (nonatomic, strong, nullable) UIColor *pageBackgroundColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *fullscreenBackgroundColor UI_APPEARANCE_SELECTOR;
- (void)enterFullscreen;
- (void)exitFullscreen;
@end

Просмотреть файл

@ -0,0 +1,111 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetsPageView.h"
@interface CTAssetsPageView ()
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation CTAssetsPageView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
_pageBackgroundColor = CTAssetsPageViewPageBackgroundColor;
_fullscreenBackgroundColor = CTAssetsPageViewFullscreenBackgroundColor;
[self setupViews];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
self.backgroundColor = self.pageBackgroundColor;
}
#pragma mark - Apperance
- (void)setPageBackgroundColor:(UIColor *)backgroundColor
{
UIColor *color = (backgroundColor) ? backgroundColor : CTAssetsPageViewPageBackgroundColor;
_pageBackgroundColor = color;
self.backgroundColor = color;
}
- (void)setFullscreenBackgroundColor:(UIColor *)fullscreenBackgroundColor
{
UIColor *color = (fullscreenBackgroundColor) ? fullscreenBackgroundColor : CTAssetsPageViewFullscreenBackgroundColor;
_fullscreenBackgroundColor = color;
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[self autoPinEdgesToSuperviewEdges];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
#pragma mark - Fading views
- (void)enterFullscreen
{
[UIView animateWithDuration:0.2
animations:^{
self.backgroundColor = self.fullscreenBackgroundColor;
}];
}
- (void)exitFullscreen
{
[UIView animateWithDuration:0.2
animations:^{
self.backgroundColor = self.pageBackgroundColor;
}];
}
@end

Просмотреть файл

@ -0,0 +1,69 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A view controller that shows selected photos and vidoes from user's photo library that let the user navigate the item page by page.
*/
@interface CTAssetsPageViewController : UIPageViewController
/**
* The index of the photo or video with the currently showing item.
*/
@property (nonatomic, assign) NSInteger pageIndex;
/**
* @name Creating a Assets Page View Controller
*/
/**
* Initializes a newly created view controller with a fetech result.
*
* @param fetchResult A fetch result of `PHAsset` objects.
*
* @return An instance of `CTAssetPageViewController` initialized to show the asset items in `fetchResult`.
*/
- (instancetype)initWithFetchResult:(PHFetchResult *)fetchResult;
/**
* Initializes a newly created view controller with an array of assets.
*
* @param assets An array of `PHAsset` objects.
*
* @return An instance of `CTAssetPageViewController` initialized to show the asset items in `assets`.
*/
- (instancetype)initWithAssets:(NSArray<PHAsset*> *)assets;
@end
NS_ASSUME_NONNULL_END

Просмотреть файл

@ -0,0 +1,422 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import "CTAssetsPageViewController.h"
#import "CTAssetsPageView.h"
#import "CTAssetItemViewController.h"
#import "CTAssetScrollView.h"
#import "NSNumberFormatter+CTAssetsPickerController.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
#import "PHAsset+CTAssetsPickerController.h"
@interface CTAssetsPageViewController ()
<UIPageViewControllerDataSource, UIPageViewControllerDelegate>
@property (nonatomic, assign) BOOL allowsSelection;
@property (nonatomic, assign, getter = isStatusBarHidden) BOOL statusBarHidden;
@property (nonatomic, copy) NSArray *assets;
@property (nonatomic, strong, readonly) PHAsset *asset;
@property (nonatomic, strong) CTAssetsPageView *pageView;
@property (nonatomic, strong) UIBarButtonItem *playButton;
@property (nonatomic, strong) UIBarButtonItem *pauseButton;
@end
@implementation CTAssetsPageViewController
- (instancetype)initWithFetchResult:(PHFetchResult *)fetchResult
{
NSMutableArray *assets = [NSMutableArray new];
for (PHAsset *asset in fetchResult)
[assets addObject:asset];
return [self initWithAssets:[NSArray arrayWithArray:assets]];
}
- (instancetype)initWithAssets:(NSArray *)assets
{
self = [super initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:@{UIPageViewControllerOptionInterPageSpacingKey:@30.f}];
if (self)
{
self.assets = assets;
self.dataSource = self;
self.delegate = self;
self.allowsSelection = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
[self addNotificationObserver];
}
- (void)dealloc
{
[self removeNotificationObserver];
}
- (BOOL)prefersStatusBarHidden
{
if (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact)
return YES;
else
return self.isStatusBarHidden;
}
#pragma mark - Setup
- (void)setupViews
{
self.pageView = [CTAssetsPageView new];
[self.view insertSubview:self.pageView atIndex:0];
[self.view setNeedsUpdateConstraints];
}
- (void)setupButtons
{
if (!self.playButton)
{
UIImage *playImage = [UIImage ctassetsPickerImageNamed:@"PlayButton"];
playImage = [playImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIBarButtonItem *playButton =
[[UIBarButtonItem alloc] initWithImage:playImage style:UIBarButtonItemStyleDone target:self action:@selector(playAsset:)];
self.playButton = playButton;
}
if (!self.pauseButton)
{
UIImage *pasueImage = [UIImage ctassetsPickerImageNamed:@"PauseButton"];
pasueImage = [pasueImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIBarButtonItem *pauseButton =
[[UIBarButtonItem alloc] initWithImage:pasueImage style:UIBarButtonItemStylePlain target:self action:@selector(pauseAsset:)];
self.pauseButton = pauseButton;
}
}
#pragma mark - Update title
- (void)updateTitle:(NSInteger)index
{
NSNumberFormatter *nf = [NSNumberFormatter new];
NSInteger count = self.assets.count;
self.title = [NSString stringWithFormat:CTAssetsPickerLocalizedString(@"%@ of %@", nil),
[nf ctassetsPickerStringFromAssetsCount:index],
[nf ctassetsPickerStringFromAssetsCount:count]];
}
#pragma mark - Update toolbar
- (void)updateToolbar
{
[self setupButtons];
if ([self.asset ctassetsPickerIsVideo])
self.toolbarItems = @[[self toolbarSpace], self.playButton, [self toolbarSpace]];
else
self.toolbarItems = nil;
}
- (void)replaceToolbarButton:(UIBarButtonItem *)button
{
if (button)
{
UIBarButtonItem *space = [self toolbarSpace];
self.toolbarItems = @[space, button, space];
}
}
- (UIBarButtonItem *)toolbarSpace
{
return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
}
#pragma mark - Accessors
- (NSInteger)pageIndex
{
return [self.assets indexOfObject:self.asset];
}
- (void)setPageIndex:(NSInteger)pageIndex
{
NSInteger count = self.assets.count;
if (pageIndex >= 0 && pageIndex < count)
{
PHAsset *asset = self.assets[pageIndex];
CTAssetItemViewController *page = [CTAssetItemViewController assetItemViewControllerForAsset:asset];
page.allowsSelection = self.allowsSelection;
[self setViewControllers:@[page]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
[self updateTitle:pageIndex + 1];
[self updateToolbar];
}
}
- (PHAsset *)asset
{
return ((CTAssetItemViewController *)self.viewControllers[0]).asset;
}
#pragma mark - Page view controller data source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
PHAsset *asset = ((CTAssetItemViewController *)viewController).asset;
NSInteger index = [self.assets indexOfObject:asset];
if (index > 0)
{
PHAsset *beforeAsset = self.assets[(index - 1)];
CTAssetItemViewController *page = [CTAssetItemViewController assetItemViewControllerForAsset:beforeAsset];
page.allowsSelection = self.allowsSelection;
return page;
}
return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
PHAsset *asset = ((CTAssetItemViewController *)viewController).asset;
NSInteger index = [self.assets indexOfObject:asset];
NSInteger count = self.assets.count;
if (index < count - 1)
{
PHAsset *afterAsset = self.assets[(index + 1)];
CTAssetItemViewController *page = [CTAssetItemViewController assetItemViewControllerForAsset:afterAsset];
page.allowsSelection = self.allowsSelection;
return page;
}
return nil;
}
#pragma mark - Page view controller delegate
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if (completed)
{
CTAssetItemViewController *vc = (CTAssetItemViewController *)pageViewController.viewControllers[0];
NSInteger index = [self.assets indexOfObject:vc.asset] + 1;
[self updateTitle:index];
[self updateToolbar];
}
}
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers
{
[self.navigationController setToolbarHidden:YES animated:YES];
}
#pragma mark - Notification observer
- (void)addNotificationObserver
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(assetScrollViewDidTap:)
name:CTAssetScrollViewDidTapNotification
object:nil];
[center addObserver:self
selector:@selector(assetScrollViewPlayerDidPlayToEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
[center addObserver:self
selector:@selector(assetScrollViewPlayerWillPlay:)
name:CTAssetScrollViewPlayerWillPlayNotification
object:nil];
[center addObserver:self
selector:@selector(assetScrollViewPlayerWillPause:)
name:CTAssetScrollViewPlayerWillPauseNotification
object:nil];
}
- (void)removeNotificationObserver
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:CTAssetScrollViewDidTapNotification object:nil];
[center removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
[center removeObserver:self name:CTAssetScrollViewPlayerWillPlayNotification object:nil];
[center removeObserver:self name:CTAssetScrollViewPlayerWillPauseNotification object:nil];
}
#pragma mark - Notification events
- (void)assetScrollViewDidTap:(NSNotification *)notification
{
UITapGestureRecognizer *gesture = (UITapGestureRecognizer *)notification.object;
if (gesture.numberOfTapsRequired == 1)
[self toggleFullscreen:gesture];
}
- (void)assetScrollViewPlayerDidPlayToEnd:(NSNotification *)notification
{
[self replaceToolbarButton:self.playButton];
[self setFullscreen:NO];
}
- (void)assetScrollViewPlayerWillPlay:(NSNotification *)notification
{
[self replaceToolbarButton:self.pauseButton];
[self setFullscreen:YES];
}
- (void)assetScrollViewPlayerWillPause:(NSNotification *)notification
{
[self replaceToolbarButton:self.playButton];
}
#pragma mark - Toggle fullscreen
- (void)toggleFullscreen:(id)sender
{
[self setFullscreen:!self.isStatusBarHidden];
}
- (void)setFullscreen:(BOOL)fullscreen
{
if (fullscreen)
{
[self.pageView enterFullscreen];
[self fadeAwayControls:self.navigationController];
}
else
{
[self.pageView exitFullscreen];
[self fadeInControls:self.navigationController];
}
}
- (void)fadeInControls:(UINavigationController *)nav
{
self.statusBarHidden = NO;
[nav setNavigationBarHidden:NO];
nav.navigationBar.alpha = 0.0f;
if ([self.asset ctassetsPickerIsVideo])
{
[nav setToolbarHidden:NO];
nav.toolbar.alpha = 0.0f;
}
[UIView animateWithDuration:0.2
animations:^{
[self setNeedsStatusBarAppearanceUpdate];
nav.navigationBar.alpha = 1.0f;
if ([self.asset ctassetsPickerIsVideo])
nav.toolbar.alpha = 1.0f;
}];
}
- (void)fadeAwayControls:(UINavigationController *)nav
{
self.statusBarHidden = YES;
[UIView animateWithDuration:0.2
animations:^{
[self setNeedsStatusBarAppearanceUpdate];
[nav setNavigationBarHidden:YES animated:NO];
[nav setToolbarHidden:YES animated:NO];
nav.navigationBar.alpha = 0.0f;
nav.toolbar.alpha = 0.0f;
}];
}
#pragma mark - Playback
- (void)playAsset:(id)sender
{
[((CTAssetItemViewController *)self.viewControllers[0]) playAsset:sender];
}
- (void)pauseAsset:(id)sender
{
[((CTAssetItemViewController *)self.viewControllers[0]) pauseAsset:sender];
}
@end

Просмотреть файл

@ -0,0 +1,31 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface CTAssetsPickerAccessDeniedView : UIView
@end

Просмотреть файл

@ -0,0 +1,126 @@
/*
MIT License (MIT)
Copyright (c) 2015 Clement CN Tsang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <PureLayout/PureLayout.h>
#import "CTAssetsPickerDefines.h"
#import "CTAssetsPickerAccessDeniedView.h"
#import "NSBundle+CTAssetsPickerController.h"
#import "UIImage+CTAssetsPickerController.h"
@interface CTAssetsPickerAccessDeniedView ()
@property (nonatomic, strong) UIImageView *padlock;
@property (nonatomic, strong) UILabel *title;
@property (nonatomic, strong) UILabel *message;
@property (nonatomic, assign) BOOL didSetupConstraints;
@end;
@implementation CTAssetsPickerAccessDeniedView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self setupViews];
}
return self;
}
#pragma mark - Setup
- (void)setupViews
{
UIImageView *padlock = [self padlockImageView];
self.padlock = padlock;
UILabel *title = [UILabel new];
title.textColor = CTAssetsPikcerAccessDeniedViewTextColor;
title.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
title.textAlignment = NSTextAlignmentCenter;
title.numberOfLines = 5;
title.text = CTAssetsPickerLocalizedString(@"This app does not have access to your photos or videos.", nil);
self.title = title;
UILabel *message = [UILabel new];
message.textColor = CTAssetsPikcerAccessDeniedViewTextColor;
message.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
message.textAlignment = NSTextAlignmentCenter;
message.numberOfLines = 5;
message.text = CTAssetsPickerLocalizedString(@"You can enable access in Privacy Settings.", nil);
self.message = message;
[self addSubview:self.padlock];
[self addSubview:self.title];
[self addSubview:self.message];
}
- (UIImageView *)padlockImageView
{
UIImage *image = [UIImage ctassetsPickerImageNamed:@"AccessDeniedViewLock"];
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImageView *padlock = [[UIImageView alloc] initWithImage:image];
padlock.tintColor = CTAssetsPikcerAccessDeniedViewTextColor;
return padlock;
}
#pragma mark - Update auto layout constraints
- (void)updateConstraints
{
if (!self.didSetupConstraints)
{
[self autoCenterInSuperview];
// suggested solution for issue #176
[self autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:self.layoutMargins.top];
[self autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:self.layoutMargins.bottom];
[self.padlock autoAlignAxisToSuperviewAxis:ALAxisVertical];
[self.padlock autoPinEdgeToSuperviewEdge:ALEdgeTop];
[self.title autoAlignAxis:ALAxisVertical toSameAxisOfView:self.padlock];
[self.title autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.padlock withOffset:20];
[self.title autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[self.title autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[self.message autoAlignAxis:ALAxisVertical toSameAxisOfView:self.padlock];
[self.message autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.title withOffset:10];
[self.message autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeTop];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
@end

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 9.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 264 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 389 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 240 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 332 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 594 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 859 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 647 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 865 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 442 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 596 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 853 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1007 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 346 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 483 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 551 B

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше