開発する前にざっとドキュメントを読んでみる。
冒頭のWidgetの振る舞いについて意訳をしてみる。
Ensure that content always looks up to date
コンテツはいつも最新にしておけよ!
Respond appropriately to user interactions
いい感じで使いやすくしろよ!
Perform well (in particular, iOS widgets must use memory wisely or the system may terminate them)
ちゃんと動かせよ!
Avoid putting a scroll view inside a Today widget.
scroll viewは使わないで!
これってUIScrollViewのことじゃないよね???
なのでpageControlとscrollViewを利用して実装してみる。
Widget出来上がりサンプル
以下のような仮実装をしてみた。
>ボタンを押して画面遷移をさせるようにしてみた。
サンプルソース
TodayView.swift
import UIKit
import Foundation
public class TodayView : UIView {
var currentPage: Int?
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame : CGRect) {
super.init(frame : frame)
}
convenience init(_ currentPage: Int) {
self.init(frame: CGRectZero)
self.setup(currentPage)
}
func setup(currentPage: Int) {
self.currentPage = currentPage
}
}
TodayViewControllerサンプル
import UIKit
import NotificationCenter
class TodayViewController: UIViewController, NCWidgetProviding {
@IBOutlet weak var pageControl: UIPageControl!
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var nextButton: UIButton!
@IBOutlet weak var backButton: UIButton!
let numberOfPages: Int = 5 // ページ数
let viewHeight: CGFloat = 140 // 画面の高さ
override func viewDidLoad() {
super.viewDidLoad()
// pageControlの準備
pageControl.numberOfPages = numberOfPages
// scrollViewの準備
scrollView.contentSize = CGSize(
width: CGFloat(numberOfPages) * scrollView.frame.size.width,
height: viewHeight
)
// Widgetに高さを明示的に教える
self.preferredContentSize = CGSizeMake(0, viewHeight);
self.updateScrollViewContent()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(NCUpdateResult.NewData)
}
// 戻るボタンアクション
@IBAction func backButton(sender: UIButton) {
if pageControl.currentPage > 0 {
pageControl.currentPage--
nextButton.hidden = false
}
if pageControl.currentPage == 0 {
backButton.hidden = true
}
self.updateScrollViewContentOffset()
}
// 進むボタンアクション
@IBAction func nextButton(sender: UIButton) {
if pageControl.currentPage < numberOfPages {
pageControl.currentPage++
backButton.hidden = false
}
if pageControl.currentPage == numberOfPages-1 {
nextButton.hidden = true
}
self.updateScrollViewContentOffset()
}
// pageControlのcurretPageValueが更新された時の処理
@IBAction func pageChanged(sender: UIPageControl) {
self.updateScrollViewContentOffset()
}
// 横スクロールのオフセットをページ開始に合わせる
func updateScrollViewContentOffset() {
scrollView.setContentOffset(
CGPoint(
x: scrollView.frame.size.width * CGFloat(pageControl.currentPage),
y: 0.0
),
animated: true
)
}
func updateScrollViewContent() {
// ページ数分の背景色を準備してみる
let colors = [
UIColor.redColor(),
UIColor.blackColor(),
UIColor.yellowColor(),
UIColor.greenColor(),
UIColor.purpleColor()
]
for var i = 0; i < numberOfPages; i++ {
let pageView = TodayView(i)
let w = scrollView.frame.size.width
let x = CGFloat(i) * w
pageView.frame = CGRectMake(x, 0, w, viewHeight)
pageView.backgroundColor = colors[i]
scrollView.addSubview(pageView)
}
}
}
APIと繋いだTodayViewControllerのコード例
まだ綺麗にできると思うけど、こんな感じでAPIからデータを取得して表示させることができる。
import UIKit
import NotificationCenter
class TodayViewController: UIViewController, NCWidgetProviding {
@IBOutlet weak var page1ViewButton: UIButton!
@IBOutlet weak var page2ViewButton: UIButton!
@IBOutlet weak var page3ViewButton: UIButton!
@IBOutlet weak var page4ViewButton: UIButton!
@IBOutlet weak var page5ViewButton: UIButton!
@IBOutlet weak var pageControl: UIPageControl!
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var nextButton: UIButton!
@IBOutlet weak var backButton: UIButton!
@IBOutlet weak var page1View: UIView!
@IBOutlet weak var page2View: UIView!
@IBOutlet weak var page3View: UIView!
@IBOutlet weak var page4View: UIView!
@IBOutlet weak var page5View: UIView!
@IBOutlet weak var page1Label: UILabel!
@IBOutlet weak var page2Label: UILabel!
@IBOutlet weak var page3Label: UILabel!
@IBOutlet weak var page4Label: UILabel!
@IBOutlet weak var page5Label: UILabel!
@IBOutlet weak var page1ImageView: UIImageView!
@IBOutlet weak var page2ImageView: UIImageView!
@IBOutlet weak var page3ImageView: UIImageView!
@IBOutlet weak var page4ImageView: UIImageView!
@IBOutlet weak var page5ImageView: UIImageView!
@IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
private var responseData: Array<Dictionary<String, AnyObject>>?
private let numberOfPages: Int = 5 // ページ数
private let viewHeight: CGFloat = 140 // 画面の高さ
private let imageURLBase: String = "https://*******"
private let imageURLSize: String = "********"
private let endpoint : String = "https://**************"
private var loading: Bool = false
private var pageViews: [UIView] = []
private var pageLabels: [UILabel] = []
private var pageImageViews: [UIImageView] = []
private var imageIds: [String] = []
private var currentCountryCode: String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.loading = false
var locale = NSLocale.currentLocale()
currentCountryCode = locale.objectForKey(NSLocaleCountryCode) as String
pageViews = [
page1View, page2View, page3View,
page4View, page5View
]
pageLabels = [
page1Label, page2Label, page3Label,
page4Label, page5Label
]
pageImageViews = [
page1ImageView, page2ImageView, page3ImageView,
page4ImageView, page5ImageView
]
// pageControlの準備
pageControl.numberOfPages = numberOfPages
// scrollViewの準備
let scrollViewWidth = scrollView.frame.size.width
scrollView.contentSize = CGSize(
width: CGFloat(numberOfPages) * scrollViewWidth,
height: viewHeight
)
for var i = 0; i < numberOfPages; i++ {
pageViews[i].frame = CGRectMake(
CGFloat(i) * scrollViewWidth, 0,
scrollViewWidth, viewHeight
)
scrollView.addSubview(pageViews[i])
pageImageViews[i].layer.cornerRadius = pageImageViews[i].frame.size.width/2
pageImageViews[i].clipsToBounds = true
}
// Widgetに高さを明示的に教える
self.preferredContentSize = CGSizeMake(0, viewHeight)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
if self.loading == false {
dispatch_async(dispatch_get_main_queue(), {
self.activityIndicatorView.startAnimating()
})
self.loading = true
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
var url = NSURL(string: self.endpoint)
let task = session.dataTaskWithURL(url!) {
(data, response, error) in
var dict = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error:nil) as Dictionary<String, Array<Dictionary<String, AnyObject>>>;
if error == nil && dict["root"] != nil {
self.responseData = dict["root"]?
if self.responseData?.count > 0 {
self.updateScrollViewContent()
completionHandler(NCUpdateResult.NewData)
} else {
completionHandler(NCUpdateResult.NoData)
}
} else {
completionHandler(NCUpdateResult.Failed)
}
self.loading = false
}
task.resume()
}
}
// 戻るボタンアクション
@IBAction func backButton(sender: UIButton) {
if pageControl.currentPage > 0 {
pageControl.currentPage--
nextButton.hidden = false
}
if pageControl.currentPage == 0 {
backButton.hidden = true
}
self.updateScrollViewContentOffset()
}
// 進むボタンアクション
@IBAction func nextButton(sender: UIButton) {
if pageControl.currentPage < numberOfPages {
pageControl.currentPage++
backButton.hidden = false
}
if pageControl.currentPage == numberOfPages-1 {
nextButton.hidden = true
}
self.updateScrollViewContentOffset()
}
// pageControlのcurretPageValueが更新された時の処理
@IBAction func pageChanged(sender: UIPageControl) {
self.updateScrollViewContentOffset()
}
@IBAction func openURL1(sender: UIButton) {
self.openURL(0)
}
@IBAction func openURL2(sender: UIButton) {
self.openURL(1)
}
@IBAction func openURL3(sender: UIButton) {
self.openURL(2)
}
@IBAction func openURL4(sender: UIButton) {
self.openURL(3)
}
@IBAction func openURL5(sender: UIButton) {
self.openURL(4)
}
func openURL(index: Int) {
var url = NSURL(string:"snapdish://dish?id=" + self.imageIds[index])
self.extensionContext?.openURL(url!, completionHandler:{
(success: Bool) -> Void in
})
}
// 横スクロールのオフセットをページ開始に合わせる
func updateScrollViewContentOffset() {
scrollView.setContentOffset(
CGPoint(
x: scrollView.frame.size.width * CGFloat(pageControl.currentPage),
y: 0.0
),
animated: true
)
}
// 画像を更新する
func updateImageInScrollViewContent(index: Int, imageId: String?) {
if let imageId = imageId? {
self.imageIds.append(imageId)
let url = NSURL(string: imageURLBase + imageId + imageURLSize);
let req = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(req, queue:NSOperationQueue.mainQueue()){
(res, data, error) in
if error == nil {
self.pageImageViews[index].image = UIImage(data: data)
} else {
self.pageImageViews[index].image = nil;
}
}
} else {
self.pageImageViews[index].image = nil;
}
}
// scrollViewの内容を更新する
func updateScrollViewContent() {
dispatch_async(dispatch_get_main_queue(), { // main threadへ移動
self.pageControl.numberOfPages = self.numberOfPages
if let newNumberOfPages = self.responseData?.count {
if newNumberOfPages < self.numberOfPages {
self.pageControl.numberOfPages = newNumberOfPages
}
}
let w = self.scrollView.frame.size.width
for i in 0..<self.numberOfPages {
if self.pageControl.numberOfPages > i {
if let dict = self.responseData?[i] {
self.pageViews[i].hidden = false
self.pageLabels[i].text = dict["title"] as? String
if let imageId = dict["image_id"] as? Dictionary<String, String> {
self.updateImageInScrollViewContent(i, imageId: imageId["$oid"])
}
}
} else {
self.pageViews[i].hidden = true
}
}
self.nextButton.hidden = false
self.activityIndicatorView.stopAnimating()
})
}
}
まとめ
今更だけどSwift書くとObjective-Cに戻れなくなりそう。直感的にサクサクかけてしまう。