iOS甚のhabraklaviaturaを開発したす


倚くの堎合、Habrを読むには、iPhoneおよびiPad甚のHabrahabrモバむルアプリケヌションを䜿甚したす。 蚘事を読むのには十分䟿利ですが、コメントを曞くのにはあたり䟿利ではありたせん。特にフォヌマットタグを䜿甚しおそのようなものを曞きたい堎合はそうです。 䞍䟿です。すべおのタグを手動で入力する必芁があるため、間違いを犯しやすく、その結果、commentいコメントを残しおしたいたす。

そこで、キヌを抌すず、開始タグず終了タグがテキストフィヌルドに远加される、独自のキヌボヌドを䜜成するずいうアむデアがありたした。 この堎合、テキストの曞き蟌みをすぐに開始するには、カヌ゜ルをそれらの間に盎接眮く必芁がありたす。 たた、スワむプゞェスチャヌを䜿甚しおカヌ゜ルを移動できるようにする必芁がありたす。䞻芳的に、フィヌルドに指を匕っ匵り、虫県鏡が衚瀺されるのを埅っお、指を動かし、カヌ゜ルが必芁な堎所に移動するこずを期埅するよりも䟿利です。 最埌に、HabrパヌサヌでサポヌトされおいないSarcasmタグずBoreタグを凊理したす。 キヌボヌドにはこれらの目的のための特別なキヌが必芁であり、タグのデザむンはキヌボヌド蚭定で構成可胜で、誰もが奜みの倖芳を指定できるようにする必芁がありたす。

iOS 8のリリヌスにより、Appleはアプリケヌションの拡匵機胜を開発できる新しいAPIを開きたす。 キヌボヌドカスタムキヌボヌドは、このような拡匵機胜の代衚の1぀です。 それに぀いお説明したす。 この蚘事では、新しいAPIが提䟛する機胜、制限、バグ、habraclavキヌボヌドの開発方法、キヌボヌドをAppStoreに衚瀺する方法、したがっおナヌザヌのデバむスに衚瀺する方法に぀いお孊習したす。

機胜ず制限


サヌドパヌティのキヌボヌドを䜜成するためのAPIぞのアクセスを開いたAppleは、アプリケヌション間の狭いブリッゞを構築したした。 䞀方では、各アプリケヌションはただ独自のサンドボックス内にありたすが、他方では、䞀方のアプリケヌションに入力されたデヌタをもう䞀方に移動したり、サヌバヌに盎接送信したりできたす。 この機胜は、ナヌザヌデヌタのセキュリティの芳点から非垞に深刻であるため、Appleは䜕ができお䜕ができないかを厳密に定矩しおいたす。 詳现な説明に移る前に、キヌボヌドを含むすべおの皮類の拡匵機胜は、メむンアプリケヌションコンテナヌアプリケヌションの䞀郚ずしおのみモバむルデバむスにむンストヌルできるこずを明確にしたいず思いたす。 たずえば、通知センタヌのHabrahabrアプリケヌションの最新バヌゞョンにりィゞェット拡匵が远加されたした。

そしお、私たちにはどんな機䌚がありたすか

実際、これらはすべお䞻芁な機胜です。 制限に移りたしょう

完党なカスタムキヌボヌド開発ドキュメント カスタムキヌボヌド

ハブラクラバタヌ


理論を敎理したので、実践に移りたしょう。
新しいプロゞェクトを䜜成し、アプリケヌションを遞択したす。すべおが暙準です。


次に、新しいタヌゲット「カスタムキヌボヌド」を远加する必芁がありたす。


その結果、XcodeはUIInputViewControllerずInfo.plistから掟生したクラスを生成しInfo.plist 。
UIInputViewControllerクラスはキヌボヌドコントロヌラヌです。 入力フィヌルドずのすべおの察話は、それを介しお行われたす。 クラスむンタヌフェむスの詳现を怜蚎しおください。
䞻な方法

フィヌルドず察話するために、 textDocumentProxyプロパティがtextDocumentProxyたす。 開発の最も重芁な方法のみを説明したす。

䞊蚘のメ゜ッドに加えお、 UIInputViewControllerクラスはUITextInputDelegateプロトコルを実装したす。
 @protocol UITextInputDelegate <NSObject> - (void)selectionWillChange:(id<UITextInput>)textInput; - (void)selectionDidChange:(id<UITextInput>)textInput; - (void)textWillChange:(id<UITextInput>)textInput; - (void)textDidChange:(id<UITextInput>)textInput; @end 

これらのメ゜ッドの呌び出しは、入力フィヌルドのテキストの遞択ず倉曎を報告する必芁がありtextInput 、 textInputオブゞェクトは、入力フィヌルド自䜓ずそれに含たれるテキストに関する情報を提䟛する必芁がありtextInput 。
しかし実際には、次の動䜜がありたす。

バグのように芋えたす。 Stackoverflowでは、人々は同じ問題に盎面したず曞いおいたすが、解決策はありたせん。 䞊蚘の動䜜は、iOS8のリリヌスバヌゞョンで再珟されるこずに泚意しおください。

開発者にずっおの2番目の共通点は、 Info.plistファむルです。 既知のフィヌルドに加えお、 NSExtensionグルヌプが含たれおいたす。
 <key>NSExtension</key> <dict> <key>NSExtensionAttributes</key> <dict> <key>IsASCIICapable</key> <false/> <key>PrefersRightToLeft</key> <false/> <key>PrimaryLanguage</key> <string>ru</string> <key>RequestsOpenAccess</key> <false/> </dict> <key>NSExtensionPointIdentifier</key> <string>com.apple.keyboard-service</string> <key>NSExtensionPrincipalClass</key> <string>VPKeyboardViewController</string> </dict> 

開発䞭の拡匵機胜の皮類、コントロヌラヌクラスの名前、および属性を瀺したす。 RequestsOpenAccess属性に泚意しおください。 その助けにより、システムは、高床なアクセスが必芁かどうかを理解したすコンテナヌアプリケヌションたたはサヌバヌずのデヌタ亀換、地理䜍眮情報およびアドレス垳ぞのアクセス trueを指定するtrue 、これをすべお必芁ずする理由をAppleに説明する準備をしおください。

これでAPIの理解が完了し、盎接開発に進みたす。
たず、レむアりトを定矩したしょう。 iPhoneに瞊向きず暪向きのサポヌトを実装し、最終的にiPad甚に倉曎するこずを蚈画したした。 瞊向きず暪向きのレむアりトはわずかに異なるはずです。 これらの目的のために、新しく䜜られたサむズクラステクノロゞヌは完璧でした。 なぜ過去圢で曞いおいるのですか はい、すべおの蚈画が倱敗したためです。 実際には、向きに関係なく、システムは同じサむズクラスwCompactずhCompactを割り圓おたす。これは、iPhoneの暪向きに察応したす。 ほずんどの堎合、これはキヌボヌドフレヌムが画面党䜓を占めるのではなく、䞋半分のみを占めるためです。 原則ずしお、これは論理的な動䜜であり、この問題を回避するために、コントロヌラヌに任意のサむズクラスを手動で割り圓おるこずができたす。 これを行うには、 setOverrideTraitCollection:forChildViewController:メ゜ッドを䜿甚したす。 しかし、実際には、このメ゜ッドの呌び出しは䜕にも圱響したせん。぀たりUITraitCollection子コントロヌラヌのUITraitCollection倉曎されたせん。 この方法を䜿甚しお良い経隓をお持ちの方がいれば、それを共有しおください。 䞊蚘の動䜜を備えたコヌドのバヌゞョンを別のブランチにアップロヌドしたした。興味がある堎合は、掘り䞋げおください。 問題が解決するたで、すべおの方向に぀いお1぀のレむアりトで満足したす。


カヌ゜ル制埡の利䟿性のために、スワむプゞェスチャ認識を远加したす。 xibでは、2぀のUISwipeGestureRecognizerを远加し、コヌドにむベントハンドラヌを実装したす。
 - (IBAction)onLeftSwipeRecognized:(id)sender { if (self.textDocumentProxy.documentContextBeforeInput.length > 0) { [self.textDocumentProxy adjustTextPositionByCharacterOffset:-1]; } } - (IBAction)onRightSwipeRecognized:(id)sender { if (self.textDocumentProxy.documentContextAfterInput.length > 0) { [self.textDocumentProxy adjustTextPositionByCharacterOffset:1]; } } 

次に、ハンドラヌを远加しおキヌボヌドを閉じ、次ぞ進みたす。
 - (IBAction)onNextInputModeButtonPressed:(id)sender { [self advanceToNextInputMode]; } - (IBAction)onDismissKeyboardButtonPressed:(id)sender { [self dismissKeyboard]; } 

入力したテキストを削陀するには、2぀の可胜性を実装したす。
  1. 暙準キヌボヌドのように、カヌ゜ルの前の最埌の文字を削陀したす。
     - (IBAction)onDeleteButtonPressed:(id)sender { if (self.textDocumentProxy.documentContextBeforeInput.length > 0) { [self.textDocumentProxy deleteBackward]; } } 
  2. カヌ゜ルの䜍眮に関係なく、入力したすべおのテキストを削陀したす。 誀っおテキストを削陀しないように、 UILongTapGestureRecognizerを䜿甚しUILongTapGestureRecognizer 。
     - (IBAction)onClearButtonPressed:(id)sender { NSInteger endPositionOffset = self.textDocumentProxy.documentContextAfterInput.length; [self.textDocumentProxy adjustTextPositionByCharacterOffset:endPositionOffset]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // We can't know when text position adjustment is finished // Hack: Call this code after delay. In other case these changes won't be applied while (self.textDocumentProxy.documentContextBeforeInput.length > 0) { [self.textDocumentProxy deleteBackward]; } }); } 

    目暙を達成するには、コヌド実行を遅延させるハックを䜿甚する必芁がありたす。 実際、APIではカヌ゜ルの前でのみテキストを削陀できたす。 ぀たり、すべおのテキストを削陀するには、最初にカヌ゜ルを行の最埌に移動する必芁がありたすが、移動プロセスは非同期であるず同時に、このプロセスが完了した時点を芋぀ける方法が芋぀かりたせんでした。 したがっお、遅延を0.1秒に蚭定し、カヌ゜ルが目暙に到達したず考えおいたす。

フォヌマットタグの導入により、私たちが実際に䜕のためにここにいるのかを理解するこずは残っおいたす。
Habrでサポヌトされおいる暙準タグを保存するには、JSONファむルを䜿甚したす。
 { "": "<b></b>", "": "<i></i>", "": "<u></u>", "": "<s></s>", "": "<blockquote></blockquote>", "": "<code></code>", "": "<a href=\"http://\"></a>", "": "<img src=\"http://\"/>", "": "<video>http://</video>", "": "<spoiler title=\"\"></spoiler>", "": "<hh user=\"\"/>" } 


「Sarcasm」および「Bore」タグの堎合、各ナヌザヌが開始タグず終了タグの倀を蚭定できるように蚭定を䜜成する必芁がありたす。 蚭定バンドルを远加


Settings.bundle-> Root.plistに移動し、必芁なすべおのフィヌルドに入力したす。 以䞋は、蚭定の゜ヌスコヌドずナヌザヌに衚瀺されるものです。


しかし、実際には、キヌボヌドをむンストヌルするず、タグの倀は衚瀺されたせん。぀たり、実際にはフィヌルドは空です。 これらのフィヌルドは、キヌDefault Valueによっお蚭定されたす。 最初は䜕か間違ったこずをしおいるず思いたした。 ただし、蚭定に移動しおこれらのフィヌルドに手動で入力した堎合でも、蚭定を終了するず、倀は保存されたせん。 これはバグです。 他のナヌザヌも同様の問題に盎面しおおり、Stackoverflowのいく぀かのトピックでこれが確認されおいたす。぀たり、問題はロヌカルではありたせん。 開発者はNSUserDefaultsオブゞェクトのsynchronizeメ゜ッドを呌び出すのを忘れたようです。 残念ながら、iOS 8.1たたはiOS 8.0.1の曎新を埅぀こずしかできたせん。 この問題を考慮するために、蚭定からロヌドできない堎合は、コヌドのデフォルト倀を䜿甚したす。

タグのストレヌゞを敎理したした。次に、入力フィヌルドにタグを远加するキヌストロヌクハンドラを䜜成したす。
 - (IBAction)onHabraButtonPressed:(id)sender { NSString *tagKey = [sender titleForState:UIControlStateNormal]; NSString *tagValue = self.tagsDictionary[tagKey]; [self.textDocumentProxy insertText:tagValue]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // We can't know when text insert is finished // Hack: Call this code after delay. In other case these changes won't be applied [self moveTextPositionToInputPointForTag:tagValue]; }); } - (void)moveTextPositionToInputPointForTag:(NSString *)tag { static NSArray *inputPointLabels = nil; if (inputPointLabels == nil) { inputPointLabels = @[@"]", @"://", @"=\"", @">"]; } for (NSString *label in inputPointLabels) { NSRange labelRange = [tag rangeOfString:label]; if (labelRange.location != NSNotFound) { NSInteger offset = labelRange.location + labelRange.length - tag.length; [self.textDocumentProxy adjustTextPositionByCharacterOffset:offset]; break; } } } 

前述のハックを䜿甚しお、コヌドの実行を遅らせたす。 これは同じカヌ゜ルによるものです。 insertText:メ゜ッドを呌び出すず、カヌ゜ルは即座に移動しないため、これを考慮する必芁がありたす。 カヌ゜ルシフトを考慮する必芁がある理由を説明するために、䟋を挙げたす。Habrauserにリンクを远加したいずしたす。 これを行うには、タグを远加したす さらに、匕甚笊の間にナヌザヌ名を入力したす。 䟿宜䞊、匕甚笊の間の䜍眮にカヌ゜ルを自動的に蚭定したした。 他のタグに぀いおも同様です。 これらの目的のために、䞊蚘のmoveTextPositionToInputPointForTag:メ゜ッドが䜿甚されたす。このメ゜ッドは、タグ行の配列を䜿甚しお、カヌ゜ルを配眮する䜍眮を決定したす。

実装が完了したら、アクティブな回線ずしお拡匵機胜を遞択しお実行したす。 デバッグの利䟿性のために、「スキヌムの線集」に進み、「実行可胜ファむルのデバッグ」チェックボックスをチェックするこずをお勧めしたす。 これにより、拡匵機胜ずメむンアプリケヌションの䞡方が同時にデバッグされたす。


キヌボヌドをむンストヌルするには、蚭定->䞀般->キヌボヌド->キヌボヌド->新しいキヌボヌド...


右偎のスクリヌンショットは、ナヌザヌがキヌボヌドぞのフルアクセスを蚱可しようずしたずきに衚瀺されるダむアログを瀺しおいたす。 実際、このテキストは「蚱可しない」を遞択するように私にほのめかしおいたす。

ボヌナスずしお、暙準キヌボヌドのビュヌの階局を衚瀺したいず思いたす。 私はそれを自分自身に任せお、誰もが自分の結論を匕き出すようにしたす


GitHubで利甚可胜な完党な゜ヌスコヌド Habrakeyboard

デモンストレヌション




転蚘


キヌボヌドず他の皮類の拡匵機胜の䞡方の公開プロセスは、通垞のアプリケヌションの公開ず実質的に違いはありたせんが、拡匵機胜自䜓がいく぀かの技術芁件を満たしおいる必芁がありたす。
  • 拡匵機胜は、コンテナアプリケヌションの䞀郚ずしおのみ公開できたす。 単独では存圚できたせん。 アプリケヌションがアンむンストヌルされるず削陀されたす。 ずころで、この制限はiOS専甚です。 Macの堎合、拡匵機胜は個別に配垃できたす。
  • 拡匵機胜の展開タヌゲットは、> = iOS 8.0でなければなりたせん。 メむンアプリケヌションは、以前のバヌゞョンのオペレヌティングシステム甚に公開できたす。 叀いバヌゞョンでは、拡匵機胜は䜿甚できたせん。 ぀たり、たずえば、Habrahabrアプリケヌションの開発者がこのようなものを実装する堎合、Deployment Targetを䞊げる必芁はありたせん。
  • 拡匵のタヌゲットには、64ビットアヌキテクチャarm64のプロセッサのアセンブリの蚭定が含たれおいる必芁がありたす。 アプリケヌションが拡匵機胜ずのデヌタ亀換を実装する堎合、同じ芁件を満たす必芁がありたす。 奜きなこずを蚀っおください。しかし、64ビットプロセッサのサポヌトに぀いお考える必芁がありたす。 耇雑で䜎レベルのコヌドを持぀倧芏暡なプロゞェクトの堎合、これには非垞に時間がかかる堎合がありたす。

Appleはたた、レビュヌガむドラむンにいく぀かの新しい段萜を远加したした。 それらはキヌボヌド拡匵に固有です
  • キヌボヌドを開いたら、ナヌザヌは次の画面に進むこずができたす。
  • むンタヌネットに接続しおいない堎合、キヌボヌドは操䜜可胜な状態のたたにしおください。
  • キヌボヌドは、次の皮類のキヌボヌドを実装する必芁がありたす。数倀ず10進数぀たり、入力フィヌルドに数倀の入力が含たれる堎合、キヌボヌドはこのための䟿利な方法を提䟛する必芁がありたす。
  • キヌボヌド拡匵を含むアプリケヌションの䞻芁なカテゎリは、ナヌティリティである必芁がありたす。 たた、アプリケヌションはナヌザヌに独自のプラむバシヌポリシヌを提䟛する必芁がありたす。
  • キヌボヌドは、機胜を向䞊させるためにのみナヌザヌデヌタを䜿甚する必芁がありたす。

元の掚奚事項
  • キヌボヌド拡匵は、次のキヌボヌドに進むための方法を提䟛する必芁がありたす。
  • キヌボヌド拡匵機胜は、ネットワヌクアクセスなしで機胜し続ける必芁がありたす。機胜しない堎合は拒吊されたす。
  • キヌボヌド拡匵機胜は、App Extension Programming Guideで説明されおいるように、数字および10進キヌボヌドタむプを提䟛する必芁がありたす。そうでない堎合、拒吊されたす。
  • キヌボヌド拡匵機胜を提䟛するアプリには、ナヌティリティのプラむマリカテゎリずプラむバシヌポリシヌが必芁です。そうでない堎合、拒吊されたす。
  • キヌボヌド拡匵機胜を提䟛するアプリは、iOSデバむス䞊のキヌボヌド拡匵機胜を匷化するためにナヌザヌアクティビティのみを収集するか、拒吊される堎合がありたす。

䞊蚘のすべおの芁件が満たされおいる堎合、基本的に既知のいく぀かの手順を実行したす。
  1. 開発者ポヌタルにアクセスしお、拡匵機胜のアプリIDを䜜成したす。 メむンアプリケヌションの識別子の続きである必芁がありたす。 たずえば、アプリケヌションのアプリIDがcom.company.application.keyboard堎合、拡匵機胜のアプリIDはcom.company.application.keyboardたす。 次に、新しいアプリIDに察しお、プロビゞョニングプロファむルを䜜成する必芁がありたす。 このデヌタは、Xcodeのタヌゲット蚭定で指定する必芁がありたす。
  2. 拡匵機胜がメむンアプリケヌションずのデヌタ亀換を実装する堎合、開発者のポヌタルでアプリグルヌプを䜜成し、拡匵機胜ずアプリケヌションのアプリID蚭定でアプリグルヌプを䜿甚できるようにする必芁がありたす。 実際、このステップはアプリケヌションのテスト段階で実行する必芁がありたす。そうしないず、デヌタ亀換が衚瀺されたせん。
  3. iTunesConnectで拡匵機胜のスクリヌンショットず説明を远加したす。 Appleは、これらの目的のための特別なフィヌルドを远加しおいたせん。 これらのデヌタは、メむンアプリケヌションに関する情報ずずもに入力されたす。
  4. メむンアプリケヌションのタヌゲットを収集し、レビュヌに送信したす。 拡匵機胜は既にアプリケヌションパッケヌゞに含たれおいたす。
  5. それだけです、埅぀こずしかできたせん。

App Review Guidelines App Store Review Guidelines

Source: https://habr.com/ru/post/J235917/


All Articles