iCloud之旅

1、创建BIDTinyPixDocument类

 1 #import <UIKit/UIKit.h>
 2 
 3 //创建文档类
 4 @interface TinyPixDocument : UIDocument
 5 //接收一对行和列索引作为参数
 6 - (BOOL)stateAtRow:(NSUInteger)row column:(NSUInteger)column;
 7 //指定的行和列设置特定的状态
 8 - (void)setState:(BOOL)state atRow:(NSUInteger)row column:(NSUInteger)column;
 9 //负责切换特定位置处的状态
10 - (void)toggleStateAtRow:(NSUInteger)row column:(NSUInteger)column;
11 
12 @end
 1 #import "TinyPixDocument.h"
 2 
 3 //类扩展
 4 @interface TinyPixDocument ()
 5 @property (nonatomic,strong) NSMutableData * bitmap;
 6 @end
 7 
 8 @implementation TinyPixDocument
 9 
10 //将每个位图初始化为从一个角延伸到另一个角的对角线图案。
11 - (id)initWithFileURL:(NSURL *)url
12 {
13     self = [super initWithFileURL:url];
14     if (self) {
15         unsigned char startPattern[] = {
16             0x01,
17             0x02,
18             0x04,
19             0x08,
20             0x10,
21             0x20,
22             0x40,
23             0x80
24         };
25         
26         self.bitmap = [NSMutableData dataWithBytes:startPattern length:8];
27     }
28     return self;
29 }
30 
31 //实现读取单个位的状态的方法。实现这个方法只要从字节数组中获取相关字节,然后对其进行位移操作和AND操作,检查是否设置了给定位,相应的返回YES或NO。
32 - (BOOL)stateAtRow:(NSUInteger)row column:(NSUInteger)column
33 {
34     const char * bitmapBytes = [self.bitmap bytes];
35     char rowByte = bitmapBytes[row];
36     char result = (1 << column) & rowByte;
37     if (result != 0) {
38         return YES;
39     } else {
40         return NO;
41     }
42 }
43 //这个方法正好和上一个相反,用于为给定行和列的位置设置值。
44 - (void)setState:(BOOL)state atRow:(NSUInteger)row column:(NSUInteger)column
45 {
46     char *bitmapBytes = [self.bitmap mutableBytes];
47     char *rowByte = &bitmapBytes[row];
48     
49     if (state) {
50         *rowByte = *rowByte | (1 << column);
51     } else {
52         *rowByte = *rowByte & ~(1 << column);
53     }
54 }
55 //辅助方法,外部代码使用该方法来切换单个单元的状态。
56 - (void)toggleStateAtRow:(NSUInteger)row column:(NSUInteger)column
57 {
58     BOOL state = [self stateAtRow:row column:column];
59     [self setState:!state atRow:row column:column];
60 }
61 
62 //保存文档时调用
63 - (id)contentsForType:(NSString *)typeName error:(NSError **)outError
64 {
65     NSLog(@"saving document to URL %@", self.fileURL);
66     return [self.bitmap copy];
67 }
68 //系统从存储区加载了数据,并且准备将这个数据提供给文档类的一个实例时,调用此方法。
69 - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName
70                    error:(NSError **)outError
71 {
72     NSLog(@"loading document from URL %@", self.fileURL);
73     self.bitmap = [contents mutableCopy];
74     return true;
75 }

2、主控制器代码

  1 #import "BIDMasterViewController.h"
  2 #import "BIDDetailViewController.h"
  3 #import "BIDTinyPixDocument.h"
  4 
  5 @interface BIDMasterViewController () <UIAlertViewDelegate>
  6 
  7 @property (weak, nonatomic) IBOutlet UISegmentedControl *colorControl;
  8 @property (strong, nonatomic) NSArray * documentFilenames;
  9 @property (strong, nonatomic) BIDTinyPixDocument * chosenDocument;
 10 
 11 @property (strong, nonatomic) NSMetadataQuery * query;
 12 @property (strong, nonatomic) NSMutableArray * documentURLs;
 13 
 14 @end
 15 
 16 @implementation BIDMasterViewController
 17 
 18 
 19 /* original
 20  //接收一个文件名作为参数,将它和应用的Document目录的文件路径结合起来,然后返回一个指向该文件的URL指针。
 21 - (NSURL *)urlForFilename:(NSString *)filename {
 22     NSFileManager * fm = [NSFileManager defaultManager];
 23     NSArray * urls = [fm URLsForDirectory:NSDocumentDirectory
 24                                inDomains:NSUserDomainMask];
 25     NSURL * directoryURL = urls[0];
 26     NSURL * fileURL = [directoryURL URLByAppendingPathComponent:filename];
 27     return fileURL;
 28 }
 29  */
 30 
 31 - (NSURL *)urlForFilename:(NSString *)filename
 32 {
 33     // be sure to insert "Documents" into the path
 34     NSURL * baseURL = [[NSFileManager defaultManager]
 35                       URLForUbiquityContainerIdentifier:nil];
 36     NSURL * pathURL = [baseURL URLByAppendingPathComponent:@"Documents"];
 37     NSURL * destinationURL = [pathURL URLByAppendingPathComponent:filename];
 38     return destinationURL;
 39 }
 40 
 41 /* original
 42  
 43  //也用到了Document目录,用于查找代表现存文档的文件。该方法获取它所找到的文件,并将它们根据创建的时间来排序,以便用户可以以“博客风格”的顺序来查看文档列表(第一个文档是最新的)。文档文件名被存放在documentFilenames属性中,然后重新加载表视图(我们尚未处理)。
 44 - (void)reloadFiles {
 45     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
 46                                                          NSUserDomainMask, YES);
 47     NSString *path = paths[0];
 48     NSFileManager *fm = [NSFileManager defaultManager];
 49     
 50     NSError *dirError;
 51     NSArray *files = [fm contentsOfDirectoryAtPath:path error:&dirError];
 52     if (!files) {
 53         NSLog(@"Encountered error while trying to list files in directory %@: %@",
 54               path, dirError);
 55     }
 56     NSLog(@"found files: %@", files);
 57     
 58     files = [files sortedArrayUsingComparator:
 59              ^NSComparisonResult(id filename1, id filename2) {
 60                  NSDictionary *attr1 = [fm attributesOfItemAtPath:
 61                                         [path stringByAppendingPathComponent:filename1]
 62                                                             error:nil];
 63                  NSDictionary *attr2 = [fm attributesOfItemAtPath:
 64                                         [path stringByAppendingPathComponent:filename2]
 65                                                             error:nil];
 66                  return [attr2[NSFileCreationDate] compare: attr1[NSFileCreationDate]];
 67              }];
 68     self.documentFilenames = files;
 69     [self.tableView reloadData];
 70 }
 71  */
 72 
 73 
 74 - (void)reloadFiles {
 75     NSFileManager * fileManager = [NSFileManager defaultManager];
 76     // passing nil is OK here, matches first entitlement
 77     NSURL * cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
 78     NSLog(@"got cloudURL %@", cloudURL);  // returns nil in simulator
 79     
 80     self.query = [[NSMetadataQuery alloc] init];
 81     _query.predicate = [NSPredicate predicateWithFormat:@"%K like '*.tinypix'",
 82                         NSMetadataItemFSNameKey];
 83     _query.searchScopes = [NSArray arrayWithObject:
 84                            NSMetadataQueryUbiquitousDocumentsScope];
 85     [[NSNotificationCenter defaultCenter]
 86      addObserver:self
 87      selector:@selector(updateUbiquitousDocuments:)
 88      name:NSMetadataQueryDidFinishGatheringNotification
 89      object:nil];
 90     [[NSNotificationCenter defaultCenter]
 91      addObserver:self
 92      selector:@selector(updateUbiquitousDocuments:)
 93      name:NSMetadataQueryDidUpdateNotification
 94      object:nil];
 95     [_query startQuery];
 96 }
 97 
 98 - (void)updateUbiquitousDocuments:(NSNotification *)notification {
 99     self.documentURLs = [NSMutableArray array];
100     self.documentFilenames = [NSMutableArray array];
101     
102     NSLog(@"updateUbiquitousDocuments, results = %@", self.query.results);
103     NSArray *results = [self.query.results sortedArrayUsingComparator:
104                         ^NSComparisonResult(id obj1, id obj2) {
105                             NSMetadataItem *item1 = obj1;
106                             NSMetadataItem *item2 = obj2;
107                             return [[item2 valueForAttribute:NSMetadataItemFSCreationDateKey] compare:
108                                     [item1 valueForAttribute:NSMetadataItemFSCreationDateKey]];
109                         }];
110     
111     for (NSMetadataItem *item in results) {
112         NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
113         [self.documentURLs addObject:url];
114         [(NSMutableArray *)_documentFilenames addObject:[url lastPathComponent]];
115     }
116     
117     [self.tableView reloadData];
118 }
119 
120 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
121     return 1;
122 }
123 
124 - (NSInteger)tableView:(UITableView *)tableView
125  numberOfRowsInSection:(NSInteger)section {
126     return [self.documentFilenames count];
127 }
128 
129 - (UITableViewCell *)tableView:(UITableView *)tableView
130          cellForRowAtIndexPath:(NSIndexPath *)indexPath {
131     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"FileCell"];
132     
133     NSString * path = self.documentFilenames[indexPath.row];
134     cell.textLabel.text = path.lastPathComponent.stringByDeletingPathExtension;
135     return cell;
136 }
137 
138 - (IBAction)chooseColor:(id)sender {
139     NSInteger selectedColorIndex = [(UISegmentedControl *)sender selectedSegmentIndex];
140     [self setTintColorForIndex:selectedColorIndex];
141 
142 //    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
143 //    [prefs setInteger:selectedColorIndex forKey:@"selectedColorIndex"];
144 //    [prefs synchronize];
145     
146     NSUbiquitousKeyValueStore * prefs = [NSUbiquitousKeyValueStore defaultStore];
147     [prefs setLongLong:selectedColorIndex forKey:@"selectedColorIndex"];
148 }
149 
150 - (void)setTintColorForIndex:(NSInteger)selectedColorIndex {
151     UIColor *tint = nil;
152     switch (selectedColorIndex) {
153         case 0:
154             tint = [UIColor redColor];
155             break;
156         case 1:
157             tint = [UIColor colorWithRed:0 green:0.6 blue:0 alpha:1];
158             break;
159         case 2:
160             tint = [UIColor blueColor];
161             break;
162         default:
163             break;
164     }
165     [UIApplication sharedApplication].keyWindow.tintColor = tint;
166 }
167 
168 - (void)viewDidAppear:(BOOL)animated {
169     [super viewDidAppear:animated];
170     
171 //    NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults];
172 //    NSInteger selectedColorIndex = [prefs integerForKey:@"selectedColorIndex"];
173     
174     NSUbiquitousKeyValueStore * prefs = [NSUbiquitousKeyValueStore defaultStore];
175     NSInteger selectedColorIndex = (int)[prefs longLongForKey:@"selectedColorIndex"];
176     
177     [self setTintColorForIndex:selectedColorIndex];
178     [self.colorControl setSelectedSegmentIndex:selectedColorIndex];
179 }
180 
181 - (void)viewDidLoad
182 {
183     [super viewDidLoad];
184     
185     UIBarButtonItem * addButton = [[UIBarButtonItem alloc]
186                                   initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
187                                   target:self
188                                   action:@selector(insertNewObject)];
189     self.navigationItem.rightBarButtonItem = addButton;
190     [self reloadFiles];
191 }
192 
193 - (void)insertNewObject {
194     // get the name
195     UIAlertView *alert =
196     [[UIAlertView alloc] initWithTitle:@"Filename"
197                                message:@"Enter a name for your new TinyPix document."
198                               delegate:self
199                      cancelButtonTitle:@"Cancel"
200                      otherButtonTitles:@"Create", nil];
201     alert.alertViewStyle = UIAlertViewStylePlainTextInput;
202     [alert show];
203 }
204 
205 - (void)alertView:(UIAlertView *)alertView
206 didDismissWithButtonIndex:(NSInteger)buttonIndex {
207     if (buttonIndex == 1) {
208         NSString *filename = [NSString stringWithFormat:@"%@.tinypix",
209                               [alertView textFieldAtIndex:0].text];
210         NSURL *saveUrl = [self urlForFilename:filename];
211         self.chosenDocument = [[BIDTinyPixDocument alloc] initWithFileURL:saveUrl];
212         [self.chosenDocument saveToURL:saveUrl
213                       forSaveOperation:UIDocumentSaveForCreating
214                      completionHandler:^(BOOL success) {
215                          if (success) {
216                              NSLog(@"save OK");
217                              [self reloadFiles];
218                              [self performSegueWithIdentifier:@"masterToDetail"
219                                                        sender:self];
220                          } else {
221                              NSLog(@"failed to save!");
222                          }
223                      }];
224     }
225 }
226 
227 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
228     if (sender == self) {
229         // if sender == self, a new document has just been created,
230         // and chosenDocument is already set.
231         
232         UIViewController * destination = segue.destinationViewController;
233         if ([destination respondsToSelector:@selector(setDetailItem:)]) {
234             [destination setValue:self.chosenDocument forKey:@"detailItem"];
235         }
236     } else {
237         // find the chosen document from the tableview
238         NSIndexPath * indexPath = [self.tableView indexPathForSelectedRow];
239         NSString * filename = self.documentFilenames[indexPath.row];
240         NSURL * docUrl = [self urlForFilename:filename];
241         self.chosenDocument = [[BIDTinyPixDocument alloc] initWithFileURL:docUrl];
242         [self.chosenDocument openWithCompletionHandler:^(BOOL success) {
243             if (success) {
244                 NSLog(@"load OK");
245                 UIViewController *destination = segue.destinationViewController;
246                 if ([destination respondsToSelector:@selector(setDetailItem:)]) {
247                     [destination setValue:self.chosenDocument forKey:@"detailItem"];
248                 }
249             } else {
250                 NSLog(@"failed to load!");
251             }
252         }];
253     }
254 }

3、创建BIDTinyPixView视图类,用于显示用户可编辑的网格。

1 #import <UIKit/UIKit.h>
2 @class BIDTinyPixDocument;
3 
4 @interface BIDTinyPixView : UIView
5 @property (strong, nonatomic) BIDTinyPixDocument * document;
6 @end
  1 #import "BIDTinyPixView.h"
  2 #import "BIDTinyPixDocument.h"
  3 
  4 typedef struct {
  5     NSUInteger row;
  6     NSUInteger column;
  7 } GridIndex;
  8 
  9 @interface BIDTinyPixView ()
 10 
 11 @property (assign, nonatomic) CGSize blockSize;
 12 @property (assign, nonatomic) CGSize gapSize;
 13 @property (assign, nonatomic) GridIndex selectedBlockIndex;
 14 
 15 @end
 16 
 17 @implementation BIDTinyPixView
 18 
 19 - (id)initWithFrame:(CGRect)frame
 20 {
 21     self = [super initWithFrame:frame];
 22     if (self) {
 23         // Initialization code
 24         [self commonInit];
 25     }
 26     return self;
 27 }
 28 
 29 - (id)initWithCoder:(NSCoder *)aDecoder {
 30     self = [super initWithCoder:aDecoder];
 31     if (self) {
 32         [self commonInit];
 33     }
 34     return self;
 35 }
 36 
 37 - (void)commonInit{
 38     _blockSize = CGSizeMake(34, 34);
 39     _gapSize = CGSizeMake(5, 5);
 40     _selectedBlockIndex.row = NSNotFound;
 41     _selectedBlockIndex.column = NSNotFound;
 42 }
 43 
 44 // Only override drawRect: if you perform custom drawing.
 45 // An empty implementation adversely affects performance during animation.
 46 - (void)drawRect:(CGRect)rect
 47 {
 48     // Drawing code
 49     if (!_document) return;
 50     
 51     for (NSUInteger row = 0; row < 8; row++) {
 52         for (NSUInteger column = 0; column < 8; column++) {
 53             [self drawBlockAtRow:row column:column];
 54         }
 55     }
 56 }
 57 
 58 - (void)drawBlockAtRow:(NSUInteger)row column:(NSUInteger)column {
 59     CGFloat startX = (_blockSize.width + _gapSize.width) * (7 - column) + 1;
 60     CGFloat startY = (_blockSize.height + _gapSize.height) * row + 1;
 61     CGRect blockFrame = CGRectMake(startX, startY, _blockSize.width, _blockSize.height);
 62     UIColor *color = [_document stateAtRow:row column:column] ?
 63         [UIColor blackColor] : [UIColor whiteColor];
 64     [color setFill];
 65     [self.tintColor setStroke];
 66     UIBezierPath *path = [UIBezierPath bezierPathWithRect:blockFrame];
 67     [path fill];
 68     [path stroke];
 69 }
 70 
 71 - (GridIndex)touchedGridIndexFromTouches:(NSSet *)touches {
 72     GridIndex result;
 73     UITouch *touch = [touches anyObject];
 74     CGPoint location = [touch locationInView:self];
 75     result.column = 8 - (location.x * 8.0 / self.bounds.size.width);
 76     result.row = location.y * 8.0 / self.bounds.size.height;
 77     return result;
 78 }
 79 
 80 - (void)toggleSelectedBlock {
 81     [_document toggleStateAtRow:_selectedBlockIndex.row
 82                          column:_selectedBlockIndex.column];
 83     [[_document.undoManager prepareWithInvocationTarget:_document]
 84      toggleStateAtRow:_selectedBlockIndex.row column:_selectedBlockIndex.column];
 85     [self setNeedsDisplay];
 86 }
 87 
 88 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 89     self.selectedBlockIndex = [self touchedGridIndexFromTouches:touches];
 90     [self toggleSelectedBlock];
 91 }
 92 
 93 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
 94     GridIndex touched = [self touchedGridIndexFromTouches:touches];
 95     if (touched.row != _selectedBlockIndex.row
 96         || touched.column != _selectedBlockIndex.column) {
 97         _selectedBlockIndex = touched;
 98         [self toggleSelectedBlock];
 99     }
100 }

4、添加BIDDetailViewController内容

1 @interface BIDDetailViewController : UIViewController
2 
3 @property (strong, nonatomic) id detailItem;
4 
5 @end
 1 #import "BIDDetailViewController.h"
 2 #import "BIDTinyPixView.h"
 3 
 4 @interface BIDDetailViewController ()
 5 @property (weak, nonatomic) IBOutlet BIDTinyPixView *pixView;
 6 - (void)configureView;
 7 @end
 8 
 9 @implementation BIDDetailViewController
10 
11 #pragma mark - Managing the detail item
12 
13 - (void)setDetailItem:(id)newDetailItem
14 {
15     if (_detailItem != newDetailItem) {
16         _detailItem = newDetailItem;
17         
18         // Update the view.
19         [self configureView];
20     }
21 }
22 
23 - (void)configureView
24 {
25     // Update the user interface for the detail item.
26 
27     if (self.detailItem) {
28         self.pixView.document = self.detailItem;
29         [self.pixView setNeedsDisplay];
30     }
31 }
32 
33 - (void)viewDidLoad
34 {
35     [super viewDidLoad];
36     // Do any additional setup after loading the view, typically from a nib.
37     [self configureView];
38 }
39 //当用户按下返回按钮回到主列表时,文档实例将在没有进行任何保存操作的情况下被销毁,所以添加如下方法。
  //一旦用户离开详情页面,就关闭文档,关闭文档会导致该文档被自动保存,保存工作发生在后台线程中。 40 - (void)viewWillDisappear:(BOOL)animated 41 { 42 [super viewWillDisappear:animated]; 43 UIDocument * doc = self.detailItem; 44 [doc closeWithCompletionHandler:nil]; 45 }

5、添加iCloud支持

创建授权文件

 

6、如何查询

@property (strong, nonatomic) NSMetadataQuery * query;
@property (strong, nonatomic) NSMutableArray * documentURLs;


- (void)reloadFiles {
    NSFileManager * fileManager = [NSFileManager defaultManager];
    // passing nil is OK here, matches first entitlement
    NSURL * cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
    NSLog(@"got cloudURL %@", cloudURL);  // returns nil in simulator
    
    self.query = [[NSMetadataQuery alloc] init];
    _query.predicate = [NSPredicate predicateWithFormat:@"%K like '*.tinypix'",
                        NSMetadataItemFSNameKey];
    _query.searchScopes = [NSArray arrayWithObject:
                           NSMetadataQueryUbiquitousDocumentsScope];
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(updateUbiquitousDocuments:)
     name:NSMetadataQueryDidFinishGatheringNotification
     object:nil];
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(updateUbiquitousDocuments:)
     name:NSMetadataQueryDidUpdateNotification
     object:nil];
    [_query startQuery];
}

//实现查询完成时的那些通知
//查询的结果包含在一个NSMetadataItem对象的列表,从中我们可以获取文件URL和创建日期等数据项,我们根据创建日期来排列这些项,然后获取所有的URL以供之后使用。
- (void)updateUbiquitousDocuments:(NSNotification *)notification {
    self.documentURLs = [NSMutableArray array];
    self.documentFilenames = [NSMutableArray array];
    
    NSLog(@"updateUbiquitousDocuments, results = %@", self.query.results);
    NSArray *results = [self.query.results sortedArrayUsingComparator:
                        ^NSComparisonResult(id obj1, id obj2) {
                            NSMetadataItem *item1 = obj1;
                            NSMetadataItem *item2 = obj2;
                            return [[item2 valueForAttribute:NSMetadataItemFSCreationDateKey] compare:
                                    [item1 valueForAttribute:NSMetadataItemFSCreationDateKey]];
                        }];
    
    for (NSMetadataItem *item in results) {
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
        [self.documentURLs addObject:url];
        [(NSMutableArray *)_documentFilenames addObject:[url lastPathComponent]];
    }
    
    [self.tableView reloadData];
}

7、保存到哪里

1 - (NSURL *)urlForFilename:(NSString *)filename
2 {
3     // be sure to insert "Documents" into the path
4     NSURL * baseURL = [[NSFileManager defaultManager]
5                       URLForUbiquityContainerIdentifier:nil];
6     NSURL * pathURL = [baseURL URLByAppendingPathComponent:@"Documents"];
7     NSURL * destinationURL = [pathURL URLByAppendingPathComponent:filename];
8     return destinationURL;
9 }

8、将首选项保存到iCloud

 1 - (IBAction)chooseColor:(id)sender {
 2     NSInteger selectedColorIndex = [(UISegmentedControl *)sender selectedSegmentIndex];
 3     [self setTintColorForIndex:selectedColorIndex];
 4 
 5 //    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
 6 //    [prefs setInteger:selectedColorIndex forKey:@"selectedColorIndex"];
 7 //    [prefs synchronize];
 8     
 9     NSUbiquitousKeyValueStore * prefs = [NSUbiquitousKeyValueStore defaultStore];
10     [prefs setLongLong:selectedColorIndex forKey:@"selectedColorIndex"];
11 }
 1 - (void)viewDidAppear:(BOOL)animated {
 2     [super viewDidAppear:animated];
 3     
 4 //    NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults];
 5 //    NSInteger selectedColorIndex = [prefs integerForKey:@"selectedColorIndex"];
 6     
 7     NSUbiquitousKeyValueStore * prefs = [NSUbiquitousKeyValueStore defaultStore];
 8     NSInteger selectedColorIndex = (int)[prefs longLongForKey:@"selectedColorIndex"];
 9     
10     [self setTintColorForIndex:selectedColorIndex];
11     [self.colorControl setSelectedSegmentIndex:selectedColorIndex];
12 }

 

posted @ 2016-04-13 11:13  FMDN  阅读(275)  评论(0编辑  收藏  举报